CrackedRuby CrackedRuby

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