Python’s pwd module reads the Unix password database (/etc/passwd), returning struct_passwd named tuples. import pwd. getpwnam: pwd.getpwnam(name) → struct_passwd; raises KeyError if not found. getpwuid: pwd.getpwuid(uid) → struct_passwd. getpwall: pwd.getpwall() → list[struct_passwd]. struct_passwd fields: pw_name (username str), pw_passwd (str, usually "x"), pw_uid (int), pw_gid (int primary group), pw_gecos (str, GECOS/full name), pw_dir (str home directory), pw_shell (str login shell). Current user: pwd.getpwuid(os.getuid()) or pwd.getpwnam(os.environ["USER"]). Home dir: pw.pw_dir or use pathlib.Path.home(). Drop privileges: call os.setgid(pw.pw_gid) then os.setuid(pw.pw_uid) (order matters). Windows: not available; use os.environ["USERNAME"] and pathlib.Path.home(). Claude Code generates user identity resolvers, home directory locators, privilege drop helpers, and user account auditors.
CLAUDE.md for pwd
## pwd Stack
- Stdlib: import pwd, os
- By name: entry = pwd.getpwnam("alice") # KeyError if missing
- By UID: entry = pwd.getpwuid(os.getuid()) # current user
- All: entries = pwd.getpwall()
- Fields: .pw_name .pw_passwd .pw_uid .pw_gid .pw_gecos .pw_dir .pw_shell
- Drop: os.setgid(pw.pw_gid); os.setuid(pw.pw_uid) # root only
pwd Unix Password Database Pipeline
# app/pwdutil.py — lookup, current user, home dir, privilege drop, audit
from __future__ import annotations
import os
import platform
from dataclasses import dataclass
from pathlib import Path
_PWD_AVAILABLE = platform.system() != "Windows"
if _PWD_AVAILABLE:
import pwd
# ─────────────────────────────────────────────────────────────────────────────
# 1. Basic lookups
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class UserEntry:
name: str
uid: int
gid: int
gecos: str
home: Path
shell: str
@property
def full_name(self) -> str:
"""Extract the full name from the GECOS field (first comma-separated value)."""
return self.gecos.split(",")[0].strip() if self.gecos else ""
def __str__(self) -> str:
return (f"uid={self.uid:5d} gid={self.gid:5d} "
f"{self.name:<16s} {self.home} {self.shell}")
def _from_struct(e) -> UserEntry:
return UserEntry(
name=e.pw_name,
uid=e.pw_uid,
gid=e.pw_gid,
gecos=e.pw_gecos,
home=Path(e.pw_dir),
shell=e.pw_shell,
)
def lookup_by_name(name: str) -> UserEntry | None:
"""
Look up a user by login name. Returns None if not found.
Example:
u = lookup_by_name("alice")
if u: print(u.home)
"""
if not _PWD_AVAILABLE:
return None
try:
return _from_struct(pwd.getpwnam(name))
except KeyError:
return None
def lookup_by_uid(uid: int) -> UserEntry | None:
"""
Look up a user by UID. Returns None if not found.
Example:
u = lookup_by_uid(os.getuid())
if u: print(u.name)
"""
if not _PWD_AVAILABLE:
return None
try:
return _from_struct(pwd.getpwuid(uid))
except KeyError:
return None
def all_users() -> list[UserEntry]:
"""
Return all user entries from the password database.
Example:
for u in all_users():
if u.uid >= 1000: print(u)
"""
if not _PWD_AVAILABLE:
return []
return [_from_struct(e) for e in pwd.getpwall()]
# ─────────────────────────────────────────────────────────────────────────────
# 2. Current user helpers
# ─────────────────────────────────────────────────────────────────────────────
def current_user() -> UserEntry | None:
"""
Return the UserEntry for the currently running process (real UID).
Example:
u = current_user()
print(u.name, u.home)
"""
return lookup_by_uid(os.getuid())
def effective_user() -> UserEntry | None:
"""
Return the UserEntry for the effective UID of the current process.
Differs from current_user() when setuid is in effect.
Example:
u = effective_user()
print(f"running as {u.name}")
"""
return lookup_by_uid(os.geteuid())
def is_root() -> bool:
"""Return True if the effective UID is 0 (root)."""
return os.geteuid() == 0
def home_dir(username: str | None = None) -> Path:
"""
Return the home directory for a user (default: current user).
Falls back to pathlib.Path.home() on Windows or lookup failure.
Example:
h = home_dir("alice")
config = h / ".config" / "myapp"
"""
if username is None:
if _PWD_AVAILABLE:
u = current_user()
return u.home if u else Path.home()
return Path.home()
if _PWD_AVAILABLE:
u = lookup_by_name(username)
return u.home if u else Path(f"/home/{username}")
return Path(f"/home/{username}")
def resolve_username(uid: int) -> str:
"""
Return the username for a UID, or the UID string if not found.
Example:
name = resolve_username(stat_result.st_uid)
"""
u = lookup_by_uid(uid)
return u.name if u else str(uid)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Privilege management
# ─────────────────────────────────────────────────────────────────────────────
class PrivilegeError(PermissionError):
"""Raised when a privilege operation cannot be completed."""
pass
def drop_privileges(username: str) -> None:
"""
Drop root privileges to the named user (irreversibly).
Must be called as root. Sets GID first, then UID.
Example:
drop_privileges("www-data")
# Process now runs as www-data
"""
if not _PWD_AVAILABLE:
raise PrivilegeError("pwd not available on Windows")
if os.geteuid() != 0:
raise PrivilegeError("drop_privileges() requires root (UID 0)")
u = lookup_by_name(username)
if u is None:
raise PrivilegeError(f"User '{username}' not found in password database")
# Set supplementary groups
try:
import grp as _grp
groups = os.getgrouplist(username, u.gid)
os.setgroups(groups)
except ImportError:
os.setgroups([u.gid])
# GID must be set before UID
os.setgid(u.gid)
os.setuid(u.uid)
def assert_not_root() -> None:
"""
Raise PrivilegeError if the process is running as root.
Useful at service start to enforce non-root operation.
Example:
assert_not_root()
start_server()
"""
if os.geteuid() == 0:
raise PrivilegeError(
"This process must not run as root. "
"Use drop_privileges() or restart with a non-root user."
)
# ─────────────────────────────────────────────────────────────────────────────
# 4. User audit
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class UserAudit:
total_users: int
system_users: int # uid < 1000
human_users: int # uid >= 1000
no_home: list[UserEntry]
nologin_users: list[UserEntry]
uid_conflicts: list[tuple[int, list[str]]] # uid → [names]
def __str__(self) -> str:
return (
f"total={self.total_users} "
f"system={self.system_users} "
f"human={self.human_users} "
f"no_home={len(self.no_home)} "
f"nologin={len(self.nologin_users)} "
f"uid_conflicts={len(self.uid_conflicts)}"
)
def audit_users(human_uid_threshold: int = 1000) -> UserAudit:
"""
Produce a summary audit of the system password database.
Example:
audit = audit_users()
print(audit)
for u in audit.no_home:
print(f" missing home: {u.name}")
"""
users = all_users()
if not users:
return UserAudit(0, 0, 0, [], [], [])
system = [u for u in users if u.uid < human_uid_threshold]
human = [u for u in users if u.uid >= human_uid_threshold]
no_home = [u for u in users if not u.home.exists() and u.uid > 0]
nologin = [u for u in users if "nologin" in u.shell or "false" in u.shell]
# Detect UID collisions (multiple names sharing a UID)
uid_map: dict[int, list[str]] = {}
for u in users:
uid_map.setdefault(u.uid, []).append(u.name)
conflicts = [(uid, names) for uid, names in uid_map.items() if len(names) > 1]
return UserAudit(
total_users=len(users),
system_users=len(system),
human_users=len(human),
no_home=no_home,
nologin_users=nologin,
uid_conflicts=conflicts,
)
def users_with_shell(shell: str) -> list[UserEntry]:
"""
Return users whose login shell contains the given substring.
Example:
bash_users = users_with_shell("/bash")
"""
return [u for u in all_users() if shell in u.shell]
def users_by_uid_range(low: int, high: int) -> list[UserEntry]:
"""
Return users whose UID falls in [low, high] (inclusive).
Example:
service_accounts = users_by_uid_range(100, 999)
"""
return [u for u in all_users() if low <= u.uid <= high]
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== pwd demo ===")
if not _PWD_AVAILABLE:
print(" pwd not available on Windows — skipping")
raise SystemExit(0)
# ── current user ───────────────────────────────────────────────────────────
print("\n--- current user ---")
u = current_user()
print(f" current: {u}")
eu = effective_user()
print(f" effective: {eu}")
print(f" is_root: {is_root()}")
print(f" home_dir(): {home_dir()}")
# ── lookup ─────────────────────────────────────────────────────────────────
print("\n--- lookups ---")
for uid in [0, os.getuid()]:
u = lookup_by_uid(uid)
print(f" uid={uid}: {u}")
for name in ["root", "nobody"]:
u = lookup_by_name(name)
print(f" {name!r}: {'found — ' + str(u) if u else 'not found'}")
# ── resolve_username ───────────────────────────────────────────────────────
print("\n--- resolve_username ---")
for uid in [0, os.getuid(), 65534]:
print(f" uid={uid} → {resolve_username(uid)!r}")
# ── shell filter ───────────────────────────────────────────────────────────
print("\n--- users_with_shell('/bash') ---")
for u in users_with_shell("/bash")[:5]:
print(f" {u}")
# ── UID range ─────────────────────────────────────────────────────────────
print("\n--- system users (UID 0–99) ---")
for u in users_by_uid_range(0, 99)[:8]:
print(f" {u}")
# ── audit ──────────────────────────────────────────────────────────────────
print("\n--- audit_users ---")
audit = audit_users()
print(f" {audit}")
if audit.uid_conflicts:
print(f" uid_conflicts: {audit.uid_conflicts[:3]}")
print("\n=== done ===")
For the grp alternative — grp.getgrnam(name) and grp.getgrgid(gid) query the group database and return struct_group entries with gr_name, gr_gid, and gr_mem (member list) — use grp for group-level identity (which users belong to a group); use pwd for user-level identity (UID, home directory, login shell). The two modules are complementary: a typical privilege check uses pwd.getpwnam() to resolve from username to UID/GID, then grp.getgrall() or os.getgrouplist() to enumerate group memberships. For the os alternative — os.getuid()/os.geteuid() return process UIDs directly without a database lookup — use os.getuid() / os.geteuid() when you only need integer UIDs for os.stat() comparisons or os.setuid() calls; use pwd when you need the username string, home path, shell, or GECOS field that the /etc/passwd database provides. The Claude Skills 360 bundle includes pwd skill sets covering lookup_by_name()/lookup_by_uid()/all_users() database readers, current_user()/effective_user()/is_root()/home_dir()/resolve_username() identity helpers, drop_privileges()/assert_not_root() privilege management, and UserAudit with audit_users()/users_with_shell()/users_by_uid_range(). Start with the free tier to try user database patterns and pwd pipeline code generation.