The grp module queries the Unix group database. It can find a group by numeric GID or name, list explicitly registered members, and translate file ownership into readable group names.
Unix groups participate in access control for files, devices, services, and system resources. However, the group database is not a complete authorization model. Effective permissions also depend on the user’s primary group, supplementary groups, mode bits, ACLs, capabilities, namespaces, SELinux, AppArmor, mount options, and other policies.
Availability
grp is available on Unix but not WASI, Android, or iOS. Windows uses a different group model.
try:
import grp
except ImportError:
grp = None
Cross-platform applications should isolate this code and retain a numeric fallback when a group name is not essential.
Group entry fields
Functions return a tuple-like object with these attributes:
gr_name, gr_passwd, gr_gid, gr_mem
gr_name is the name, gr_gid the numeric ID, and gr_mem a list of explicitly listed login names. gr_passwd is historical and normally empty or unusable.
Query by GID
import grp
try:
group = grp.getgrgid(1000)
except KeyError:
print("No group entry for this GID")
else:
print(group.gr_name, group.gr_mem)
Since Python 3.10, a non-integer argument such as a string or float raises TypeError. Validate CLI and API input before calling the function.
Query by name
import grp
try:
group = grp.getgrnam("developers")
except KeyError:
print("Unknown group")
else:
print(group.gr_gid)
Do not use an untrusted group name to make a privileged decision without resolving it and checking the process’s actual credentials.
Primary and supplementary groups
A user’s primary group is stored as pw_gid in Python pwd. The gr_mem list contains users explicitly associated with the group entry.
The official documentation warns that users are often not listed in gr_mem for their primary group. Looking only at that list therefore gives an incomplete result.
Find every group for a user
import grp
import pwd
def groups_for_user(name):
account = pwd.getpwnam(name)
gids = {account.pw_gid}
for group in grp.getgrall():
if name in group.gr_mem:
gids.add(group.gr_gid)
return [grp.getgrgid(gid) for gid in sorted(gids)]
On LDAP-connected systems or machines with many groups, getgrall() can be expensive. When available, prefer os.getgrouplist(name, primary_gid).
Groups of the current process
os.getgid() returns the real GID, os.getegid() the effective GID, and os.getgroups() supplementary GIDs.
import os
print("Real:", os.getgid())
print("Effective:", os.getegid())
print("Supplementary:", os.getgroups())
The kernel normally checks the effective GID and supplementary group list rather than reconstructing membership from static files.
Translate file ownership
import grp
import os
info = os.stat("file.txt")
try:
group_name = grp.getgrgid(info.st_gid).gr_name
except KeyError:
group_name = str(info.st_gid)
print(group_name)
Keep the numeric GID as a fallback. Files can outlive a group or come from another namespace or host.
gr_passwd is not authentication
Group passwords are a historical feature and are rarely used. gr_passwd is normally empty or contains a marker.
Do not validate credentials with it. Authentication and authorization should use PAM, service policy, ACLs, or another supported mechanism.
List all groups
import grp
for group in grp.getgrall():
print(group.gr_gid, group.gr_name, group.gr_mem)
The order is arbitrary. NSS may consult LDAP, SSSD, NIS, or another remote source, making this operation slow and potentially large.
NSS and remote identity providers
Like pwd, grp follows the Name Service Switch configuration. Entries may come from /etc/group, LDAP, SSSD, NIS, container files, or plugins.
A seemingly local call can block on network I/O. Avoid getgrall() inside every web request or file operation.
NIS references
Group names beginning with + or - may be YP/NIS references and may not be individually accessible through getgrnam() or getgrgid().
Do not treat them as ordinary local groups without understanding host configuration.
Cache with expiration
Frequent lookups may use a short-lived cache.
import time
_cache = {}
def group_by_gid(gid, ttl=60):
now = time.monotonic()
item = _cache.get(gid)
if item and now - item[0] < ttl:
return item[1]
value = grp.getgrgid(gid)
_cache[gid] = (now, value)
return value
Use a monotonic clock for TTL and do not cache failures forever.
Containers and namespaces
Inside a container, a GID may have no entry in /etc/group. Kubernetes and other platforms may also inject supplementary groups without creating names.
Do not fail when displaying ownership. Show the numeric GID. For authorization, use the credentials actually applied by the kernel.
Shared volumes
The same GID can map to different names on different hosts. For NFS and shared volumes, consistent numeric IDs matter more than display names.
Plan UID and GID allocation across hosts, containers, and services before relying on POSIX permissions.
Check current-process membership
import os
def process_in_group(gid):
return gid == os.getegid() or gid in os.getgroups()
This answers a question about the current process and is more relevant to an immediate operation than rebuilding membership from database entries.
Let the kernel make the final decision
Even a process in a group can be denied by mode bits, ACLs, mount options, SELinux, AppArmor, read-only filesystems, or capabilities.
Attempt the operation and handle PermissionError. Group queries help diagnosis but do not replace the system call.
Change a file’s group
import grp
import os
target = grp.getgrnam("developers")
os.chown("file.txt", -1, target.gr_gid)
This requires permission. Validate paths and avoid following untrusted symbolic links. Security-sensitive code should use directory-relative APIs and appropriate flags.
Change the effective group
Privileged processes can use os.setgid() or os.setegid(). These operations are security-sensitive and may be irreversible.
Prefer having the service manager start the application with the correct user and group.
initgroups()
os.initgroups(user, primary_gid) initializes supplementary groups according to system policy.
import os
import pwd
account = pwd.getpwnam("appuser")
os.initgroups(account.pw_name, account.pw_gid)
os.setgid(account.pw_gid)
os.setuid(account.pw_uid)
Call initgroups() before dropping privileges. The wrong order can prevent configuration or retain privileged groups.
Clear inherited supplementary groups
Changing only UID and primary GID may leave inherited supplementary groups that retain access.
Use initgroups() for the target account or os.setgroups([]) when the policy requires no additional groups.
Race conditions
Membership can change between a lookup and an operation. For security, rely on the system call’s authorization result and handle failure.
Do not use a check-then-use sequence as the only protection.
Privacy
Membership lists expose account names and organizational structure. Do not publish getgrall() through an API or dump it into logs without authorization and a clear need.
Logging
When reporting access failures through Python syslog, prefer numeric GID and a sanitized name. Avoid logging the full member list.
Resource isolation
Unprivileged users and groups complement the quotas described in Python resource, but neither measure forms a complete sandbox.
Testing
Test existing and missing groups, non-integer arguments, a user whose primary group omits them from gr_mem, supplementary groups, containers without names, slow LDAP, NIS, shared volumes, actual permission checks, and privilege dropping.
Unit tests should not depend on the host’s real group database. Wrap lookups and provide fake records.
Common mistakes
Common failures include treating gr_mem as complete, using gr_passwd for authentication, assuming every GID has a name, calling getgrall() in a hot path, forgetting supplementary groups while dropping privileges, and assuming membership guarantees access.
Conclusion
grp resolves Unix groups by name or GID and helps interpret ownership and membership. Combine it with pwd and the process’s effective group list for an accurate view.
Preserve numeric IDs, account for NSS and containers, never use the database for authentication, and let the kernel decide final access. Consult the official grp documentation and group(5).







