Python’s stat module provides symbolic constants and helper functions for interpreting os.stat() result objects and POSIX file mode bits. import stat. Type tests: stat.S_ISREG(mode), stat.S_ISDIR(mode), stat.S_ISLNK(mode), stat.S_ISSOCK(mode), stat.S_ISBLK(mode), stat.S_ISCHR(mode), stat.S_ISFIFO(mode) — each takes an st_mode integer (from os.stat().st_mode) and returns a bool. Extract bits: stat.S_IFMT(mode) → file type bits; stat.S_IMODE(mode) → permission bits (lower 12 bits). Permission constants: stat.S_IRUSR (0o400), stat.S_IWUSR (0o200), stat.S_IXUSR (0o100), stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP, stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH. Special bits: stat.S_ISUID (setuid), stat.S_ISGID (setgid), stat.S_ISVTX (sticky). Index constants: stat.ST_MODE, stat.ST_INO, stat.ST_DEV, stat.ST_NLINK, stat.ST_UID, stat.ST_GID, stat.ST_SIZE, stat.ST_ATIME, stat.ST_MTIME, stat.ST_CTIME — use when indexing os.stat() as a tuple. Format: stat.filemode(mode) → "-rwxr-xr-x" string. Claude Code generates permission checkers, file type routers, directory walkers, and mode formatting utilities.
CLAUDE.md for stat
## stat Stack
- Stdlib: import stat, os
- Mode: st = os.stat(path); mode = st.st_mode
- Type: stat.S_ISREG(mode) stat.S_ISDIR(mode) stat.S_ISLNK(mode)
- Perms: bool(mode & stat.S_IRUSR) # readable by owner
- stat.S_IMODE(mode) # permission bits (0o755)
- Format: stat.filemode(mode) # "-rwxr-xr-x"
- Set: os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
stat File Mode Pipeline
# app/statutil.py — mode parsing, permission checks, chmod helpers, walker
from __future__ import annotations
import os
import stat
from dataclasses import dataclass
from pathlib import Path
from typing import Iterator
# ─────────────────────────────────────────────────────────────────────────────
# 1. File type helpers
# ─────────────────────────────────────────────────────────────────────────────
def file_type(mode: int) -> str:
"""
Return a human-readable file type string for a mode integer.
Example:
st = os.stat("/etc/hosts")
print(file_type(st.st_mode)) # "regular file"
"""
tests = [
(stat.S_ISREG, "regular file"),
(stat.S_ISDIR, "directory"),
(stat.S_ISLNK, "symlink"),
(stat.S_ISSOCK, "socket"),
(stat.S_ISBLK, "block device"),
(stat.S_ISCHR, "char device"),
(stat.S_ISFIFO, "FIFO/pipe"),
]
for fn, name in tests:
if fn(mode):
return name
return "unknown"
def is_executable(path: "str | Path") -> bool:
"""
Return True if any executable bit (user/group/other) is set.
Example:
if is_executable("/usr/bin/python3"):
print("it's executable")
"""
mode = os.stat(str(path)).st_mode
return bool(mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH))
def is_world_writable(path: "str | Path") -> bool:
"""
Return True if the 'other write' bit is set (world-writable).
Example:
if is_world_writable("/tmp/shared"):
print("WARNING: world-writable")
"""
mode = os.stat(str(path)).st_mode
return bool(mode & stat.S_IWOTH)
def has_setuid(path: "str | Path") -> bool:
"""Return True if the setuid bit is set."""
return bool(os.stat(str(path)).st_mode & stat.S_ISUID)
def has_sticky(path: "str | Path") -> bool:
"""Return True if the sticky bit is set."""
return bool(os.stat(str(path)).st_mode & stat.S_ISVTX)
# ─────────────────────────────────────────────────────────────────────────────
# 2. Permission inspection
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class PermissionBits:
"""
Parsed POSIX permission bits for a file.
Example:
bits = PermissionBits.from_path("/etc/passwd")
print(bits)
if bits.world_writable:
print("SECURITY RISK")
"""
mode: int
path: str = ""
@classmethod
def from_path(cls, path: "str | Path") -> "PermissionBits":
p = Path(path)
return cls(mode=p.stat().st_mode, path=str(p))
@classmethod
def from_mode(cls, mode: int) -> "PermissionBits":
return cls(mode=mode)
@property
def octal(self) -> str:
return oct(stat.S_IMODE(self.mode))
@property
def string(self) -> str:
return stat.filemode(self.mode)
@property
def owner_read(self) -> bool:
return bool(self.mode & stat.S_IRUSR)
@property
def owner_write(self) -> bool:
return bool(self.mode & stat.S_IWUSR)
@property
def owner_exec(self) -> bool:
return bool(self.mode & stat.S_IXUSR)
@property
def group_read(self) -> bool:
return bool(self.mode & stat.S_IRGRP)
@property
def group_write(self) -> bool:
return bool(self.mode & stat.S_IWGRP)
@property
def group_exec(self) -> bool:
return bool(self.mode & stat.S_IXGRP)
@property
def other_read(self) -> bool:
return bool(self.mode & stat.S_IROTH)
@property
def other_write(self) -> bool:
return bool(self.mode & stat.S_IWOTH)
@property
def other_exec(self) -> bool:
return bool(self.mode & stat.S_IXOTH)
@property
def world_writable(self) -> bool:
return self.other_write
@property
def setuid(self) -> bool:
return bool(self.mode & stat.S_ISUID)
@property
def setgid(self) -> bool:
return bool(self.mode & stat.S_ISGID)
@property
def sticky(self) -> bool:
return bool(self.mode & stat.S_ISVTX)
def __str__(self) -> str:
flags = []
if self.setuid:
flags.append("SETUID")
if self.setgid:
flags.append("SETGID")
if self.sticky:
flags.append("STICKY")
if self.world_writable:
flags.append("WORLD-WRITABLE")
flags_str = f" [{' '.join(flags)}]" if flags else ""
return f"{self.string} {self.octal}{flags_str}"
# ─────────────────────────────────────────────────────────────────────────────
# 3. chmod helpers
# ─────────────────────────────────────────────────────────────────────────────
def chmod_octal(path: "str | Path", mode: str) -> None:
"""
Set file permissions using an octal string (e.g. "0o644", "644", "755").
Example:
chmod_octal("script.sh", "755")
chmod_octal("config.ini", "0o640")
"""
if isinstance(mode, str):
mode_int = int(mode, 8) if not mode.startswith("0o") else int(mode, 0)
else:
mode_int = mode # type: ignore[assignment]
os.chmod(str(path), mode_int)
def make_executable(path: "str | Path") -> None:
"""
Add the executable bit for owner (u+x) to a file.
Example:
make_executable("deploy.sh")
"""
p = Path(path)
current = p.stat().st_mode
p.chmod(current | stat.S_IXUSR)
def remove_world_write(path: "str | Path") -> None:
"""
Remove the world-writable bit (o-w) from a file.
Example:
remove_world_write("/etc/cron.d/myjob")
"""
p = Path(path)
current = p.stat().st_mode
p.chmod(current & ~stat.S_IWOTH)
# ─────────────────────────────────────────────────────────────────────────────
# 4. Security audit walker
# ─────────────────────────────────────────────────────────────────────────────
@dataclass
class SecurityIssue:
path: str
issue: str
mode_str: str
def audit_permissions(root: "str | Path", follow_symlinks: bool = False) -> list[SecurityIssue]:
"""
Walk root and report files with dangerous permission settings:
world-writable, setuid, setgid on regular files.
Example:
issues = audit_permissions("/etc")
for issue in issues:
print(f" {issue.path}: {issue.issue} ({issue.mode_str})")
"""
issues: list[SecurityIssue] = []
for dirpath, dirs, files in os.walk(str(root)):
for name in list(dirs) + files:
full = os.path.join(dirpath, name)
try:
st = os.lstat(full) if not follow_symlinks else os.stat(full)
except OSError:
continue
mode = st.st_mode
mode_str = stat.filemode(mode)
if stat.S_ISREG(mode) and (mode & stat.S_IWOTH):
issues.append(SecurityIssue(full, "world-writable", mode_str))
if stat.S_ISREG(mode) and (mode & stat.S_ISUID):
issues.append(SecurityIssue(full, "setuid", mode_str))
if stat.S_ISREG(mode) and (mode & stat.S_ISGID):
issues.append(SecurityIssue(full, "setgid", mode_str))
return issues
# ─────────────────────────────────────────────────────────────────────────────
# 5. stat result formatter
# ─────────────────────────────────────────────────────────────────────────────
def format_stat(path: "str | Path") -> dict:
"""
Return a human-readable dict of os.stat() fields for a path.
Example:
info = format_stat("/etc/hosts")
for k, v in info.items():
print(f" {k}: {v}")
"""
import datetime
p = Path(path)
st = p.stat()
mode = st.st_mode
return {
"path": str(p),
"type": file_type(mode),
"mode": stat.filemode(mode),
"octal": oct(stat.S_IMODE(mode)),
"size": st.st_size,
"uid": st.st_uid,
"gid": st.st_gid,
"nlink": st.st_nlink,
"mtime": datetime.datetime.fromtimestamp(st.st_mtime).isoformat(timespec="seconds"),
"atime": datetime.datetime.fromtimestamp(st.st_atime).isoformat(timespec="seconds"),
}
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile
print("=== stat demo ===")
with tempfile.TemporaryDirectory() as td:
# ── create test files ──────────────────────────────────────────────────
regular = Path(td) / "data.txt"
regular.write_text("hello")
script = Path(td) / "run.sh"
script.write_text("#!/bin/sh\necho hi\n")
subdir = Path(td) / "subdir"
subdir.mkdir()
# ── file_type ──────────────────────────────────────────────────────────
print("\n--- file_type ---")
for p in [regular, subdir]:
mode = os.stat(p).st_mode
print(f" {p.name}: {file_type(mode)}")
# ── PermissionBits ─────────────────────────────────────────────────────
print("\n--- PermissionBits ---")
bits = PermissionBits.from_path(regular)
print(f" {regular.name}: {bits}")
print(f" owner_read={bits.owner_read} owner_write={bits.owner_write}")
print(f" world_writable={bits.world_writable}")
# ── chmod_octal + make_executable ─────────────────────────────────────
print("\n--- chmod ---")
chmod_octal(regular, "644")
print(f" after chmod 644: {stat.filemode(os.stat(regular).st_mode)}")
make_executable(script)
print(f" after u+x: {stat.filemode(os.stat(script).st_mode)}")
# ── format_stat ───────────────────────────────────────────────────────
print("\n--- format_stat ---")
info = format_stat(regular)
for k, v in info.items():
print(f" {k}: {v}")
# ── world-writable audit ──────────────────────────────────────────────
print("\n--- audit_permissions ---")
os.chmod(regular, 0o666) # make world-writable
issues = audit_permissions(td)
for issue in issues:
rel = Path(issue.path).name
print(f" {rel}: {issue.issue} ({issue.mode_str})")
# ── stat constants ────────────────────────────────────────────────────
print("\n--- common mode constants ---")
for name in ["S_IRUSR", "S_IWUSR", "S_IXUSR", "S_IRGRP", "S_IROTH", "S_ISUID", "S_ISVTX"]:
val = getattr(stat, name)
print(f" stat.{name} = {oct(val)}")
print("\n=== done ===")
For the pathlib.Path.stat() / pathlib.Path.chmod() alternative — Path.stat() returns the same os.stat_result as os.stat(), and Path.chmod(mode) sets permissions — use pathlib for the object-oriented API when you already have a Path object; import stat for the mode constants and helper functions (S_ISREG, filemode, etc.) regardless — they are complementary, not alternatives. For the os.access alternative — os.access(path, os.R_OK | os.W_OK) checks the effective UID’s permissions (honours setuid/setgid) rather than raw mode bits — use os.access when you need to know if the current process can actually read or write a file; use stat when you need to inspect or set the raw permission bits regardless of effective UID. The Claude Skills 360 bundle includes stat skill sets covering file_type()/is_executable()/is_world_writable()/has_setuid()/has_sticky() mode tests, PermissionBits with all owner_*/group_*/other_* properties and world_writable/setuid/sticky, chmod_octal()/make_executable()/remove_world_write() permission setters, audit_permissions() security walker returning SecurityIssue records, and format_stat() human-readable stat dict. Start with the free tier to try file mode patterns and stat pipeline code generation.