Overview
File permissions control access to files and directories in operating systems. They determine which users and processes can read, write, or execute specific filesystem resources. Operating systems enforce these permissions at the kernel level, providing the foundation for multi-user security and access control.
Unix-like systems use a permission model based on three permission types (read, write, execute) applied to three entity classes (owner, group, others). Each file has an owner user and an associated group. The permission bits determine what operations each class can perform. Windows uses Access Control Lists (ACLs), which provide more granular control through security descriptors containing security identifiers (SIDs) and access control entries (ACEs).
File permissions affect system security, application behavior, and deployment configurations. Incorrectly configured permissions create security vulnerabilities or prevent applications from functioning. Understanding permission models enables developers to write secure file handling code, diagnose permission-related failures, and implement appropriate access controls.
# Check if file is readable
File.readable?('/etc/passwd')
# => true
# Get octal permission representation
File.stat('/etc/passwd').mode.to_s(8)
# => "100644"
Key Principles
File permissions operate on the principle of least privilege: grant the minimum access necessary for functionality. This reduces the attack surface and limits damage from compromised processes or malicious code.
Permission Types
Read permission allows viewing file contents or listing directory contents. For files, read access permits opening and reading data. For directories, read access allows listing contained files and subdirectories but not accessing their metadata or contents without execute permission.
Write permission allows modifying file contents or directory structure. For files, write access permits modifying, truncating, or appending data. For directories, write access allows creating, deleting, and renaming files within the directory, but only when combined with execute permission.
Execute permission allows running files as programs or traversing directories. For files, execute access permits loading and running the file as a program. For directories, execute access (sometimes called search permission) allows accessing files within the directory and retrieving file metadata. Without execute permission on a directory, read permission becomes nearly useless.
Entity Classes
Owner permissions apply to the user who owns the file. Every file has exactly one owner, typically the user who created it. The owner can modify the file's permissions (subject to system policies).
Group permissions apply to members of the file's associated group. Unix systems assign each file a group owner, and users belonging to that group receive the group permissions.
Other permissions apply to all users who are neither the owner nor members of the group. This class represents the general public on the system.
Permission Representation
Unix systems represent permissions using octal notation, where each digit encodes three permission bits. The number 4 represents read, 2 represents write, and 1 represents execute. Summing these values creates combinations: 7 (rwx), 6 (rw-), 5 (r-x), 4 (r--), 3 (-wx), 2 (-w-), 1 (--x), 0 (---).
A full permission set uses four octal digits. The first digit represents special bits (setuid, setgid, sticky), while the remaining three represent owner, group, and other permissions respectively. The common permission 0644 means owner has read+write (6), while group and others have read-only (4).
Special Permission Bits
The setuid bit (4000) causes executable files to run with the owner's user ID rather than the executor's user ID. This allows specific programs to perform privileged operations. Common examples include passwd and sudo.
The setgid bit (2000) on executables causes programs to run with the file's group ID. On directories, setgid causes newly created files to inherit the directory's group rather than the creating user's primary group.
The sticky bit (1000) on directories restricts deletion: only the file owner, directory owner, or root can delete or rename files within the directory. This protects shared directories like /tmp from unauthorized file deletion.
Ruby Implementation
Ruby provides file permission management through the File and FileTest modules. The File class contains methods for both querying and modifying permissions, while FileTest provides predicate methods for permission checks.
Querying Permissions
The File.stat method returns a File::Stat object containing permission information. The mode method returns permissions as an integer, with the lower 12 bits representing Unix permissions.
# Get file statistics
stat = File.stat('example.txt')
# Get full mode including file type
mode = stat.mode
# => 33188 (decimal representation)
# Convert to octal string for readability
octal_mode = mode.to_s(8)
# => "100644"
# Extract permission bits only (last 9 bits)
permissions = mode & 0777
permissions.to_s(8)
# => "644"
Ruby provides convenient predicate methods for common permission checks. These methods accept either filenames or File objects.
# Check read permission
File.readable?('example.txt')
# => true
# Check write permission
File.writable?('example.txt')
# => true
# Check execute permission
File.executable?('script.sh')
# => false
# Check if file is owned by effective user
File.owned?('example.txt')
# => true
# Check if file is owned by effective group
File.growned?('example.txt')
# => false
Setting Permissions
The File.chmod method modifies file permissions using octal notation. It accepts either a single file or an array of files.
# Set permissions to 0644 (rw-r--r--)
File.chmod(0644, 'example.txt')
# Set permissions on multiple files
File.chmod(0755, 'script.sh', 'deploy.sh')
# Set directory permissions recursively requires manual traversal
Dir.glob('**/*').each do |file|
File.chmod(0644, file) if File.file?(file)
File.chmod(0755, file) if File.directory?(file)
end
The umask determines default permissions for newly created files. Ruby provides File.umask to query and set the umask value.
# Get current umask
current = File.umask
# => 18 (decimal for 022 octal)
# Set new umask (returns old value)
old_umask = File.umask(0027)
# Create file with default permissions affected by umask
File.open('newfile.txt', 'w') { |f| f.write('data') }
# Restore original umask
File.umask(old_umask)
Ownership Changes
The File.chown method changes file ownership, requiring appropriate privileges (typically root).
# Change owner and group by numeric IDs
File.chown(1000, 1000, 'example.txt')
# Get numeric IDs from Etc module
require 'etc'
user = Etc.getpwnam('username')
group = Etc.getgrnam('groupname')
File.chown(user.uid, group.gid, 'example.txt')
# Change owner only (group unchanged)
File.chown(user.uid, -1, 'example.txt')
# Change group only (owner unchanged)
File.chown(-1, group.gid, 'example.txt')
File::Stat Detailed Information
File::Stat provides methods for extracting specific permission details and file attributes.
stat = File.stat('example.txt')
# Get owner user ID
stat.uid
# => 1000
# Get owner group ID
stat.gid
# => 1000
# Check for special bits
stat.setuid?
# => false
stat.setgid?
# => false
stat.sticky?
# => false
# Get file type
stat.file?
# => true
stat.directory?
# => false
stat.symlink?
# => false
Practical Examples
Setting Up Application Deployment Permissions
Applications require specific permission configurations for secure operation. Configuration files should be readable by the application but protected from other users. Log files need write access for the application. Executable scripts require execute permissions.
# Set up Rails application permissions
APP_USER = 1000
APP_GROUP = 1000
# Configuration files: owner read/write, others no access
Dir.glob('config/**/*.yml').each do |config|
File.chmod(0600, config)
File.chown(APP_USER, APP_GROUP, config)
end
# Log directory: owner read/write/execute
File.chmod(0700, 'log')
File.chown(APP_USER, APP_GROUP, 'log')
# Public assets: world readable
Dir.glob('public/**/*').each do |asset|
if File.directory?(asset)
File.chmod(0755, asset)
else
File.chmod(0644, asset)
end
File.chown(APP_USER, APP_GROUP, asset)
end
# Scripts: owner execute
Dir.glob('bin/*').each do |script|
File.chmod(0750, script)
File.chown(APP_USER, APP_GROUP, script)
end
Creating Secure Temporary Files
Temporary files require careful permission handling to prevent information disclosure or unauthorized modification. Creating files with restrictive permissions from the start prevents race conditions.
require 'tempfile'
# Tempfile creates files with 0600 by default
temp = Tempfile.new('secure')
stat = File.stat(temp.path)
stat.mode.to_s(8)
# => "100600"
# Manual secure temporary file creation
secure_temp = File.join(Dir.tmpdir, "secure-#{Process.pid}")
File.open(secure_temp, File::CREAT | File::EXCL | File::WRONLY, 0600) do |f|
f.write('sensitive data')
end
# Verify permissions before use
unless File.stat(secure_temp).mode & 0077 == 0
raise SecurityError, 'Temporary file has unsafe permissions'
end
Implementing Permission Checks Before Operations
Applications should verify permissions before attempting operations to provide clear error messages and avoid exposing system details through exception backtraces.
class FileProcessor
def process(filename)
# Check file exists
unless File.exist?(filename)
raise ArgumentError, "File not found: #{filename}"
end
# Check read permission
unless File.readable?(filename)
raise SecurityError, "Cannot read file: #{filename}"
end
# Check file size to avoid processing empty files
if File.zero?(filename)
raise ArgumentError, "File is empty: #{filename}"
end
# Perform actual processing
File.open(filename, 'r') do |f|
f.each_line { |line| process_line(line) }
end
end
def process_line(line)
# Implementation
end
end
Managing Shared Directory Permissions
Shared directories require careful configuration to allow collaboration while preventing unauthorized access or deletion.
# Create shared directory with setgid and sticky bit
shared_dir = '/var/shared/project'
Dir.mkdir(shared_dir) unless Dir.exist?(shared_dir)
# Set permissions: rwxrwsr-t (2775 + sticky bit)
File.chmod(03775, shared_dir)
# Set group ownership
require 'etc'
project_group = Etc.getgrnam('project')
File.chown(-1, project_group.gid, shared_dir)
# Verify configuration
stat = File.stat(shared_dir)
permissions = stat.mode & 07777
# Check setgid bit
if stat.setgid?
puts "setgid is set - new files inherit group"
end
# Check sticky bit
if stat.sticky?
puts "Sticky bit set - only owners can delete their files"
end
# Permission should be 3775
if permissions.to_s(8) == '3775'
puts "Shared directory configured correctly"
end
Security Implications
File permissions form the primary access control mechanism in Unix-like systems. Misconfigured permissions create security vulnerabilities ranging from information disclosure to privilege escalation.
World-Writable Files and Directories
World-writable permissions (mode includes write for others) allow any user to modify the file or directory. This creates opportunities for malicious users to inject code, modify data, or create denial-of-service conditions.
Configuration files, executable scripts, and application code should never be world-writable. Even user data files should restrict write access unless sharing is explicitly required. World-writable directories without the sticky bit allow any user to delete other users' files.
# Check for dangerous world-writable permissions
def check_world_writable(path)
stat = File.stat(path)
mode = stat.mode & 0777
# Check if others have write permission
if mode & 0002 != 0
if stat.directory? && !stat.sticky?
puts "WARNING: #{path} is world-writable without sticky bit"
elsif !stat.directory?
puts "CRITICAL: #{path} is world-writable"
end
end
end
# Scan directory tree for world-writable files
Dir.glob('**/*').each { |file| check_world_writable(file) }
Readable Sensitive Data
Sensitive data like configuration files containing passwords, API keys, or cryptographic keys requires restrictive permissions. Group-readable or world-readable permissions expose credentials to unauthorized users.
Database configuration files, private keys, and authentication tokens should have 0600 permissions (owner read/write only). Even within application directories, restricting access prevents exposure through directory traversal vulnerabilities or misconfigured web servers.
# Secure configuration file handling
class SecureConfig
def self.load(path)
# Verify permissions before reading
stat = File.stat(path)
mode = stat.mode & 0777
# Must be owned by current user
unless stat.owned?
raise SecurityError, "Config file not owned by current user"
end
# Must not be readable by group or others
if mode & 0077 != 0
raise SecurityError, "Config file has insecure permissions: #{mode.to_s(8)}"
end
# Safe to read
YAML.load_file(path)
end
end
Executable Permission Vulnerabilities
Execute permission on files determines whether they can run as programs. Granting execute permission to data files or configuration files creates opportunities for code execution. Combined with write permissions, attackers can replace legitimate files with malicious code.
Applications should validate that executable files have appropriate permissions before execution. Scripts loaded by interpreters (Ruby, Python, shell) should not have write permissions for other users.
# Validate script before execution
def safe_execute(script_path, *args)
stat = File.stat(script_path)
# Must be a regular file
unless stat.file?
raise SecurityError, "Not a regular file: #{script_path}"
end
# Must be executable by owner
unless File.executable?(script_path)
raise SecurityError, "File not executable: #{script_path}"
end
# Must not be writable by others
if stat.mode & 0002 != 0
raise SecurityError, "Script is world-writable: #{script_path}"
end
# Execute script
system(script_path, *args)
end
Privilege Escalation Through setuid/setgid
Files with setuid or setgid bits run with elevated privileges, creating security risks if the program contains vulnerabilities or can be modified. These special bits should be used sparingly and only on well-audited programs.
Ruby scripts cannot effectively use setuid because interpreters ignore the setuid bit for security reasons. Applications requiring privilege elevation should use alternative mechanisms like sudo or capabilities.
# Detect setuid/setgid files
def find_privileged_files(directory)
privileged = []
Dir.glob(File.join(directory, '**/*')).each do |file|
next unless File.file?(file)
stat = File.stat(file)
if stat.setuid?
privileged << { file: file, type: 'setuid', uid: stat.uid }
end
if stat.setgid?
privileged << { file: file, type: 'setgid', gid: stat.gid }
end
end
privileged
end
Default Permissions and umask
The umask controls default permissions for newly created files and directories. A permissive umask (like 0000) creates files with excessive permissions by default. Applications should set an appropriate umask before creating files.
The standard umask 0022 creates files with 0644 (rw-r--r--) and directories with 0755 (rwxr-xr-x). For security-sensitive applications, a more restrictive umask like 0027 or 0077 prevents unintended information disclosure.
# Create file with secure default permissions
def create_secure_file(path)
# Set restrictive umask temporarily
old_umask = File.umask(0077)
begin
File.open(path, 'w') do |f|
yield f
end
ensure
# Restore original umask
File.umask(old_umask)
end
end
# Usage
create_secure_file('credentials.yml') do |f|
f.write(YAML.dump(credentials))
end
# Verify restrictive permissions
File.stat('credentials.yml').mode.to_s(8)
# => "100600"
Common Pitfalls
Assuming Consistent Permission Behavior Across Platforms
Windows and Unix have fundamentally different permission models. Code that works correctly on Unix may fail or behave unexpectedly on Windows. Windows uses ACLs instead of simple permission bits, and many Unix permission operations have limited or no effect on Windows.
Ruby's File.chmod on Windows maps Unix permissions to Windows ACLs approximately, but setgid, setuid, and sticky bits have no meaning. Applications targeting multiple platforms must handle these differences explicitly.
# Platform-aware permission handling
def set_secure_permissions(path)
if Gem.win_platform?
# Windows: ACL-based approach
# Ruby's File.chmod has limited effect
# Consider using win32-security gem for proper ACL management
File.chmod(0600, path) # Best effort
else
# Unix: Standard permission bits
File.chmod(0600, path)
# Verify it worked
mode = File.stat(path).mode & 0777
unless mode == 0600
raise SecurityError, "Failed to set secure permissions"
end
end
end
Forgetting Execute Permission on Directories
Directories require execute permission for access to their contents. Read permission alone allows listing filenames but not accessing files or their metadata. This commonly causes unexpected "Permission denied" errors when accessing files in readable directories.
Applications creating directories should grant execute permission along with read permission. The standard directory permissions 0755 or 0750 include execute for intended users.
# Incorrect: Directory without execute permission
Dir.mkdir('data')
File.chmod(0600, 'data') # rw------- (no execute)
# Attempting to access file inside fails
File.open('data/file.txt', 'w')
# => Errno::EACCES: Permission denied
# Correct: Include execute permission
Dir.mkdir('data')
File.chmod(0700, 'data') # rwx------ (includes execute)
File.open('data/file.txt', 'w') { |f| f.write('content') }
# => Success
Race Conditions Between Permission Checks and Operations
Checking permissions before operations creates a time-of-check-time-of-use (TOCTOU) race condition. The file's permissions or ownership might change between the check and the actual operation.
Malicious users can exploit this window to substitute files or modify permissions. Applications should rely on operation failures rather than preliminary checks when security matters. Exception handling provides safer error management than permission predicates.
# Vulnerable: TOCTOU race condition
if File.writable?(path)
# File permissions might change here
File.open(path, 'w') { |f| f.write(data) }
end
# Safer: Handle operation failure
begin
File.open(path, 'w') { |f| f.write(data) }
rescue Errno::EACCES
# Handle permission denied
puts "Cannot write to #{path}: Permission denied"
rescue Errno::ENOENT
# Handle file not found
puts "File not found: #{path}"
end
Modifying Permissions on Symlinks
Ruby's File.chmod follows symbolic links by default, modifying the target file's permissions rather than the link itself. This can have unintended consequences when processing directory trees containing symlinks.
Applications should detect symlinks and handle them appropriately. File.lchmod changes symlink permissions directly on systems supporting it, but many Unix systems don't support this operation.
# Check for symlinks before modifying permissions
def safe_chmod(mode, path)
if File.symlink?(path)
puts "Skipping symlink: #{path}"
return
end
File.chmod(mode, path)
end
# Recursive permission change that respects symlinks
Dir.glob('**/*').each do |file|
safe_chmod(0644, file) if File.file?(file) && !File.symlink?(file)
end
Forgetting to Restore umask
Changing the umask affects all subsequently created files in the process. Forgetting to restore the original umask after creating files with special permissions causes unintended permission defaults for other operations.
Applications should save the original umask and restore it using ensure blocks. This pattern guarantees restoration even when exceptions occur.
# Incorrect: umask not restored
File.umask(0077)
create_sensitive_files()
# All subsequent file creation uses restrictive umask
# Correct: Guaranteed restoration
old_umask = File.umask(0077)
begin
create_sensitive_files()
ensure
File.umask(old_umask)
end
Insufficient Permissions on Parent Directories
File access requires appropriate permissions on all parent directories in the path. A file with permissive permissions becomes inaccessible if any parent directory lacks execute permission for the user.
Applications should verify the entire path has appropriate permissions, not just the target file. This particularly matters when creating files in nested directory structures.
# Create nested directories with appropriate permissions
def create_file_with_path(path, mode = 0644)
dir = File.dirname(path)
# Create parent directories if needed
unless Dir.exist?(dir)
FileUtils.mkdir_p(dir)
# Ensure directories have execute permission
File.chmod(0755, dir)
end
# Create file with specified permissions
old_umask = File.umask(0)
begin
File.open(path, 'w', mode) { |f| yield f if block_given? }
ensure
File.umask(old_umask)
end
end
Integration & Interoperability
Cross-Platform Permission Handling
Windows ACLs provide granular access control beyond Unix permission bits. Ruby provides basic cross-platform abstractions, but full Windows ACL manipulation requires platform-specific code or gems like win32-security.
Applications targeting both platforms should use conservative permissions that work everywhere. Avoid relying on special bits (setuid, setgid, sticky) that have no Windows equivalent.
# Cross-platform permission abstraction
class PermissionManager
def self.set_private_file(path)
if windows?
# Windows: Attempt to restrict via Ruby's abstraction
File.chmod(0600, path)
# For production Windows deployments, use ACL APIs
else
# Unix: Standard permission bits
File.chmod(0600, path)
verify_unix_permissions(path, 0600)
end
end
def self.set_shared_directory(path)
if windows?
# Windows: Basic permissions
File.chmod(0755, path)
else
# Unix: Use setgid for group inheritance
File.chmod(02775, path)
verify_unix_permissions(path, 02775)
end
end
private
def self.windows?
Gem.win_platform?
end
def self.verify_unix_permissions(path, expected)
actual = File.stat(path).mode & 07777
unless actual == expected
raise SecurityError, "Permission verification failed"
end
end
end
Container and Virtual Environment Considerations
Containers and virtual environments add complexity to permission management. Container processes run as specific users, and mounted volumes must have appropriate permissions for the container user.
Docker containers commonly run as root inside the container but map to non-root users on the host. Volume mounts require matching UIDs/GIDs or using user namespaces for correct permission handling.
# Check effective permissions for container environment
def container_permissions_check
puts "Process UID: #{Process.uid}"
puts "Process GID: #{Process.gid}"
puts "Process EUID: #{Process.euid}"
puts "Process EGID: #{Process.egid}"
# Check if running as root
if Process.uid == 0
puts "WARNING: Running as root in container"
end
# Verify access to mounted volumes
['/app/data', '/app/logs', '/app/config'].each do |mount|
if Dir.exist?(mount)
readable = File.readable?(mount)
writable = File.writable?(mount)
puts "#{mount}: read=#{readable}, write=#{writable}"
else
puts "#{mount}: NOT MOUNTED"
end
end
end
Network Filesystem Implications
Network filesystems like NFS introduce additional permission complexity. NFS traditionally uses Unix permissions but relies on client-server UID/GID mapping. Mismatched user databases between client and server cause permission mismatches.
NFSv4 added ACL support and improved security, but configuration requires careful attention. Applications should handle permission errors gracefully when working with network filesystems.
# Detect and handle network filesystem permission issues
def safe_network_file_operation(path)
begin
File.open(path, 'w') { |f| f.write('data') }
rescue Errno::EACCES
# Check if file is on network filesystem
stat = File.stat(File.dirname(path))
# Network filesystems often have different stat values
if potential_network_fs?(stat)
puts "Permission denied on potential network filesystem"
puts "Check UID/GID mapping and export options"
end
raise
end
end
def potential_network_fs?(stat)
# NFS typically has different device numbers and fsid
# This is heuristic and platform-specific
stat.dev > 100 # Simplified check
end
Working with setcap and Capabilities
Modern Linux systems use capabilities for fine-grained privilege management instead of setuid. Capabilities allow granting specific privileges (like binding to low ports) without full root access.
Ruby programs cannot directly manipulate capabilities, but they inherit capabilities from the parent process. Deployment scripts should use setcap to grant necessary capabilities to Ruby executables.
# Check if process has specific capabilities (requires root or CAP_SYS_ADMIN)
def check_capabilities
# Read capabilities from /proc/self/status
caps = File.read('/proc/self/status')
.lines
.grep(/^Cap/)
.map { |line| line.strip }
puts "Process capabilities:"
caps.each { |cap| puts " #{cap}" }
# Check for common capability (CAP_NET_BIND_SERVICE = 0x0000000000000400)
# This is platform-specific and requires libcap parsing
end
# Check if we can bind to privileged port
def can_bind_privileged_port?
begin
server = TCPServer.new('0.0.0.0', 80)
server.close
true
rescue Errno::EACCES
false
end
end
Reference
Permission Modes
| Octal | Binary | Symbolic | Description |
|---|---|---|---|
| 0 | 000 | --- | No permissions |
| 1 | 001 | --x | Execute only |
| 2 | 010 | -w- | Write only |
| 3 | 011 | -wx | Write and execute |
| 4 | 100 | r-- | Read only |
| 5 | 101 | r-x | Read and execute |
| 6 | 110 | rw- | Read and write |
| 7 | 111 | rwx | Read, write, and execute |
Common Permission Patterns
| Mode | Use Case | Description |
|---|---|---|
| 0600 | Private files | Owner read/write only |
| 0644 | Public readable files | Owner write, all read |
| 0700 | Private directories | Owner full access only |
| 0755 | Public directories | Owner write, all read/execute |
| 0750 | Group accessible | Owner full, group read/execute |
| 0640 | Group readable files | Owner write, group read |
| 0777 | World writable | All permissions for everyone (avoid) |
| 02775 | Shared directory | Group sticky, all read/write/execute |
| 01777 | Public sticky directory | Sticky bit, all permissions |
Special Permission Bits
| Octal | Name | Files | Directories |
|---|---|---|---|
| 4000 | setuid | Execute as file owner | No effect |
| 2000 | setgid | Execute with file group | New files inherit directory group |
| 1000 | sticky | No effect | Only owners can delete their files |
Ruby File Permission Methods
| Method | Returns | Description |
|---|---|---|
| File.chmod(mode, path) | Integer | Changes file permissions |
| File.chown(uid, gid, path) | Integer | Changes file ownership |
| File.umask | Integer | Gets current umask |
| File.umask(mask) | Integer | Sets umask, returns old value |
| File.stat(path) | File::Stat | Returns file statistics |
| File.readable?(path) | Boolean | Checks read permission |
| File.writable?(path) | Boolean | Checks write permission |
| File.executable?(path) | Boolean | Checks execute permission |
| File.owned?(path) | Boolean | Checks if owned by effective user |
| File.growned?(path) | Boolean | Checks if owned by effective group |
File::Stat Permission Methods
| Method | Returns | Description |
|---|---|---|
| stat.mode | Integer | Full mode including type bits |
| stat.uid | Integer | Owner user ID |
| stat.gid | Integer | Owner group ID |
| stat.owned? | Boolean | True if owned by effective user |
| stat.growned? | Boolean | True if owned by effective group |
| stat.setuid? | Boolean | True if setuid bit set |
| stat.setgid? | Boolean | True if setgid bit set |
| stat.sticky? | Boolean | True if sticky bit set |
Security Checklist
| Check | Risk Level | Mitigation |
|---|---|---|
| World-writable files | Critical | Set maximum 0664 permissions |
| World-writable directories without sticky | High | Add sticky bit or remove write |
| Credentials with group/other read | Critical | Set 0600 permissions |
| Executable data files | High | Remove execute permission |
| Missing directory execute | Medium | Add execute permission |
| Permissive default umask | Medium | Set umask 0022 or stricter |
| setuid/setgid programs | High | Audit or remove if unnecessary |
| Inconsistent ownership | Medium | Use consistent UID/GID |
Platform-Specific Behavior
| Feature | Unix/Linux | Windows | macOS |
|---|---|---|---|
| Permission bits | Full support | Limited mapping to ACLs | Full support |
| setuid/setgid | Supported | Not supported | Supported |
| Sticky bit | Supported | Not supported | Supported |
| File.chmod | Precise control | Approximate mapping | Precise control |
| File.chown | Requires privileges | Limited support | Requires privileges |
| Symlink permissions | Link or target configurable | Follows target | Follows target |
| ACL support | Extended attributes | Native | Extended attributes |