File Protection on macOS
Protect critical files and applications across your MacFleet devices using advanced file protection techniques, immutable flags, and enterprise security policies. This tutorial provides comprehensive tools for implementing organizational file security standards.
Understanding macOS File Protection Methods
macOS offers several file protection mechanisms:
- Immutable Flags - System-level protection preventing modification or deletion
- File Permissions - User and group-based access controls
- Extended Attributes - Additional metadata for security enforcement
- System Integrity Protection (SIP) - Built-in protection for system files
Basic File Protection Commands
Lock Single File
#!/bin/bash
# Lock a file using immutable flag
FILE_PATH="/Applications/WhatsApp.app"
chflags schg "$FILE_PATH"
echo "File locked: $FILE_PATH"
Unlock Single File
#!/bin/bash
# Unlock a file by removing immutable flag
FILE_PATH="/Applications/WhatsApp.app"
chflags noschg "$FILE_PATH"
echo "File unlocked: $FILE_PATH"
Check File Protection Status
#!/bin/bash
# Check if file is protected
FILE_PATH="/Applications/WhatsApp.app"
if ls -lO "$FILE_PATH" | grep -q "schg"; then
echo "File is protected: $FILE_PATH"
else
echo "File is not protected: $FILE_PATH"
fi
Enterprise File Protection System
#!/bin/bash
# MacFleet Enterprise File Protection System
# Comprehensive file security and protection management
# Configuration
MACFLEET_DIR="/etc/macfleet"
POLICIES_DIR="$MACFLEET_DIR/file_policies"
REPORTS_DIR="$MACFLEET_DIR/reports"
COMPLIANCE_DIR="$MACFLEET_DIR/compliance"
AUDIT_DIR="$MACFLEET_DIR/audit"
LOG_FILE="/var/log/macfleet_file_protection.log"
BACKUP_DIR="$MACFLEET_DIR/backups"
# Create directory structure
create_directories() {
local dirs=("$MACFLEET_DIR" "$POLICIES_DIR" "$REPORTS_DIR" "$COMPLIANCE_DIR" "$AUDIT_DIR" "$BACKUP_DIR")
for dir in "${dirs[@]}"; do
[[ ! -d "$dir" ]] && mkdir -p "$dir"
done
}
# Logging function
log_action() {
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $message" | tee -a "$LOG_FILE"
}
# File Categories for Enterprise Protection
declare -A FILE_CATEGORIES=(
["critical_apps"]="/Applications/System Preferences.app,/Applications/Terminal.app,/Applications/Utilities"
["business_apps"]="/Applications/Microsoft Office,/Applications/Slack.app,/Applications/Zoom.app"
["system_configs"]="/etc/hosts,/etc/sudoers,/etc/ssh/sshd_config"
["security_tools"]="/Applications/1Password.app,/Applications/Keychain Access.app"
["development_tools"]="/Applications/Xcode.app,/Applications/Visual Studio Code.app"
["admin_utilities"]="/Applications/Activity Monitor.app,/Applications/Console.app"
)
# Protection Levels
declare -A PROTECTION_LEVELS=(
["maximum"]="schg,uappnd,uchg" # System immutable, user append-only, user immutable
["high"]="schg,uchg" # System immutable, user immutable
["moderate"]="uchg" # User immutable only
["minimal"]="uappnd" # User append-only
["readonly"]="ro" # Read-only access
)
# Security Policies
declare -A SECURITY_POLICIES=(
["enterprise_lockdown"]="critical_apps:maximum,business_apps:high,system_configs:maximum,security_tools:high"
["standard_protection"]="critical_apps:high,system_configs:high,security_tools:moderate"
["development_safe"]="critical_apps:moderate,system_configs:high,development_tools:minimal"
["minimal_security"]="system_configs:moderate,security_tools:minimal"
["maintenance_mode"]="system_configs:readonly"
)
# Apply protection to file or directory
protect_file() {
local file_path="$1"
local protection_level="$2"
local policy="$3"
if [[ ! -e "$file_path" ]]; then
log_action "ERROR: File not found: $file_path"
return 1
fi
# Backup current flags
local current_flags=$(ls -lO "$file_path" 2>/dev/null | awk '{print $5}' || echo "none")
echo "$file_path:$current_flags:$(date)" >> "$BACKUP_DIR/file_flags_backup.log"
# Apply protection based on level
local flags="${PROTECTION_LEVELS[$protection_level]}"
if [[ -z "$flags" ]]; then
log_action "ERROR: Unknown protection level: $protection_level"
return 1
fi
# Split flags and apply each one
IFS=',' read -ra flag_array <<< "$flags"
for flag in "${flag_array[@]}"; do
if chflags "$flag" "$file_path" 2>/dev/null; then
log_action "Applied protection flag '$flag' to: $file_path (Policy: $policy)"
else
log_action "ERROR: Failed to apply flag '$flag' to: $file_path"
fi
done
# Set restrictive permissions if needed
if [[ "$protection_level" == "maximum" || "$protection_level" == "high" ]]; then
chmod 644 "$file_path" 2>/dev/null
log_action "Set restrictive permissions for: $file_path"
fi
# Save protection metadata
echo "file=$file_path,protection=$protection_level,policy=$policy,timestamp=$(date),user=$(whoami)" >> "$POLICIES_DIR/applied_protections.log"
}
# Remove protection from file
unprotect_file() {
local file_path="$1"
local reason="$2"
if [[ ! -e "$file_path" ]]; then
log_action "ERROR: File not found: $file_path"
return 1
fi
# Remove all common protection flags
local flags_to_remove=("schg" "uchg" "uappnd" "ro")
for flag in "${flags_to_remove[@]}"; do
if chflags "no$flag" "$file_path" 2>/dev/null; then
log_action "Removed protection flag '$flag' from: $file_path (Reason: $reason)"
fi
done
# Restore standard permissions
if [[ -f "$file_path" ]]; then
chmod 644 "$file_path" 2>/dev/null
elif [[ -d "$file_path" ]]; then
chmod 755 "$file_path" 2>/dev/null
fi
log_action "File unprotected: $file_path (Reason: $reason)"
}
# Apply protection to file category
protect_category() {
local category="$1"
local protection_level="$2"
local policy="$3"
if [[ -z "${FILE_CATEGORIES[$category]}" ]]; then
log_action "ERROR: Unknown file category: $category"
return 1
fi
log_action "Protecting category: $category with level: $protection_level (Policy: $policy)"
# Split comma-separated file paths
IFS=',' read -ra files <<< "${FILE_CATEGORIES[$category]}"
for file_path in "${files[@]}"; do
# Handle wildcard patterns
if [[ "$file_path" == *"*"* ]]; then
# Use find for pattern matching
find "${file_path%/*}" -name "${file_path##*/}" 2>/dev/null | while read -r found_file; do
protect_file "$found_file" "$protection_level" "$policy"
done
else
protect_file "$file_path" "$protection_level" "$policy"
fi
done
}
# Apply comprehensive security policy
apply_security_policy() {
local policy="$1"
if [[ -z "${SECURITY_POLICIES[$policy]}" ]]; then
log_action "ERROR: Unknown security policy: $policy"
return 1
fi
log_action "Applying security policy: $policy"
# Parse policy string: "category:level,category:level"
IFS=',' read -ra policy_items <<< "${SECURITY_POLICIES[$policy]}"
for item in "${policy_items[@]}"; do
IFS=':' read -ra parts <<< "$item"
local category="${parts[0]}"
local protection_level="${parts[1]}"
protect_category "$category" "$protection_level" "$policy"
done
log_action "Security policy '$policy' applied successfully"
# Generate compliance report
generate_compliance_report "$policy"
}
# Bulk file protection operations
bulk_protect_files() {
local file_list="$1"
local protection_level="$2"
local policy="$3"
if [[ ! -f "$file_list" ]]; then
log_action "ERROR: File list not found: $file_list"
return 1
fi
log_action "Starting bulk protection operation from list: $file_list"
local protected_count=0
local failed_count=0
while IFS= read -r file_path; do
[[ -z "$file_path" || "$file_path" =~ ^#.*$ ]] && continue
if protect_file "$file_path" "$protection_level" "$policy"; then
((protected_count++))
else
((failed_count++))
fi
done < "$file_list"
log_action "Bulk protection completed: $protected_count protected, $failed_count failed"
}
# Critical system file protection
protect_system_files() {
local protection_mode="$1"
log_action "Applying system file protection: $protection_mode"
# Define critical system files
local critical_files=(
"/etc/hosts"
"/etc/sudoers"
"/etc/ssh/sshd_config"
"/etc/passwd"
"/etc/group"
"/System/Library/LaunchDaemons"
"/Library/LaunchDaemons"
"/usr/bin/sudo"
"/bin/sh"
"/bin/bash"
)
case "$protection_mode" in
"lockdown")
for file in "${critical_files[@]}"; do
if [[ -e "$file" ]]; then
protect_file "$file" "maximum" "system_lockdown"
fi
done
;;
"standard")
for file in "${critical_files[@]}"; do
if [[ -e "$file" ]]; then
protect_file "$file" "high" "system_standard"
fi
done
;;
"minimal")
for file in "${critical_files[@]}"; do
if [[ -e "$file" ]]; then
protect_file "$file" "moderate" "system_minimal"
fi
done
;;
esac
log_action "System file protection applied: $protection_mode"
}
# File integrity monitoring
monitor_file_integrity() {
local monitoring_mode="$1"
local monitor_file="$POLICIES_DIR/integrity_monitor.log"
log_action "Starting file integrity monitoring: $monitoring_mode"
# Get list of protected files
local protected_files=()
if [[ -f "$POLICIES_DIR/applied_protections.log" ]]; then
while IFS=',' read -r entry; do
if [[ "$entry" =~ file=([^,]+) ]]; then
protected_files+=("${BASH_REMATCH[1]}")
fi
done < "$POLICIES_DIR/applied_protections.log"
fi
# Monitor files for changes
for file in "${protected_files[@]}"; do
if [[ -e "$file" ]]; then
local current_hash=$(shasum -a 256 "$file" 2>/dev/null | cut -d' ' -f1)
local current_flags=$(ls -lO "$file" 2>/dev/null | awk '{print $5}' || echo "none")
local timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)
echo "$timestamp:$file:$current_hash:$current_flags" >> "$monitor_file"
fi
done
log_action "File integrity monitoring completed: $monitoring_mode"
}
# Emergency unlock procedures
emergency_unlock() {
local reason="$1"
local unlock_scope="$2"
log_action "EMERGENCY UNLOCK INITIATED - Reason: $reason, Scope: $unlock_scope"
case "$unlock_scope" in
"all")
# Unlock all protected files
if [[ -f "$POLICIES_DIR/applied_protections.log" ]]; then
while IFS=',' read -r entry; do
if [[ "$entry" =~ file=([^,]+) ]]; then
unprotect_file "${BASH_REMATCH[1]}" "emergency_unlock"
fi
done < "$POLICIES_DIR/applied_protections.log"
fi
;;
"system")
# Unlock only system files
local system_files=("/etc/hosts" "/etc/sudoers" "/etc/ssh/sshd_config")
for file in "${system_files[@]}"; do
if [[ -e "$file" ]]; then
unprotect_file "$file" "emergency_system_unlock"
fi
done
;;
"applications")
# Unlock application files
find /Applications -name "*.app" -exec chflags noschg {} \; 2>/dev/null
log_action "Emergency unlock completed for applications"
;;
esac
# Create emergency unlock record
echo "emergency_unlock:$reason:$unlock_scope:$(date):$(whoami)" >> "$AUDIT_DIR/emergency_operations.log"
log_action "Emergency unlock completed: $unlock_scope"
}
# Generate comprehensive compliance report
generate_compliance_report() {
local policy="$1"
local report_file="$REPORTS_DIR/file_protection_compliance_$(date +%Y%m%d_%H%M%S).json"
local protected_files_count=0
local total_files_checked=0
# Count protected files
if [[ -f "$POLICIES_DIR/applied_protections.log" ]]; then
protected_files_count=$(wc -l < "$POLICIES_DIR/applied_protections.log")
fi
# Count total files in categories
for category in "${!FILE_CATEGORIES[@]}"; do
IFS=',' read -ra files <<< "${FILE_CATEGORIES[$category]}"
total_files_checked=$((total_files_checked + ${#files[@]}))
done
cat > "$report_file" << EOF
{
"report_metadata": {
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"hostname": "$(hostname)",
"policy_applied": "$policy",
"report_version": "1.0"
},
"file_protection_status": {
"protected_files_count": $protected_files_count,
"total_files_checked": $total_files_checked,
"protection_coverage": $(echo "scale=2; $protected_files_count * 100 / $total_files_checked" | bc -l 2>/dev/null || echo "0"),
"emergency_unlocks_recorded": $([ -f "$AUDIT_DIR/emergency_operations.log" ] && wc -l < "$AUDIT_DIR/emergency_operations.log" || echo "0")
},
"security_policy": {
"name": "$policy",
"categories_protected": $(echo "${!FILE_CATEGORIES[@]}" | wc -w),
"compliance_frameworks": ["SOX", "HIPAA", "NIST", "ISO27001", "PCI-DSS"]
},
"system_status": {
"sip_enabled": $(csrutil status | grep -q "enabled" && echo "true" || echo "false"),
"gatekeeper_enabled": $(spctl --status | grep -q "enabled" && echo "true" || echo "false"),
"filevault_enabled": $(fdesetup status | grep -q "On" && echo "true" || echo "false"),
"backup_available": $([ -f "$BACKUP_DIR/file_flags_backup.log" ] && echo "true" || echo "false")
},
"integrity_monitoring": {
"active": $([ -f "$POLICIES_DIR/integrity_monitor.log" ] && echo "true" || echo "false"),
"last_check": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"monitored_files": $([ -f "$POLICIES_DIR/integrity_monitor.log" ] && wc -l < "$POLICIES_DIR/integrity_monitor.log" || echo "0")
}
}
EOF
log_action "Compliance report generated: $report_file"
echo "Report saved to: $report_file"
}
# Health check and validation
perform_health_check() {
echo "=== MacFleet File Protection Health Check ==="
# Check system integrity protection
local sip_status=$(csrutil status 2>/dev/null || echo "Unknown")
echo "✓ System Integrity Protection: $sip_status"
# Check protected files
local protected_count=0
if [[ -f "$POLICIES_DIR/applied_protections.log" ]]; then
protected_count=$(wc -l < "$POLICIES_DIR/applied_protections.log")
fi
echo "✓ Protected files: $protected_count"
# Check critical system files
local critical_protected=0
local critical_files=("/etc/hosts" "/etc/sudoers" "/etc/ssh/sshd_config")
for file in "${critical_files[@]}"; do
if [[ -e "$file" ]] && ls -lO "$file" | grep -q "schg"; then
((critical_protected++))
fi
done
echo "✓ Critical system files protected: $critical_protected/${#critical_files[@]}"
# Check backup availability
if [[ -f "$BACKUP_DIR/file_flags_backup.log" ]]; then
local backup_entries=$(wc -l < "$BACKUP_DIR/file_flags_backup.log")
echo "✓ Backup entries: $backup_entries"
else
echo "○ No backup records found"
fi
# Check integrity monitoring
if [[ -f "$POLICIES_DIR/integrity_monitor.log" ]]; then
local monitor_entries=$(wc -l < "$POLICIES_DIR/integrity_monitor.log")
echo "✓ Integrity monitoring entries: $monitor_entries"
else
echo "○ No integrity monitoring active"
fi
# Check for emergency operations
if [[ -f "$AUDIT_DIR/emergency_operations.log" ]]; then
local emergency_count=$(wc -l < "$AUDIT_DIR/emergency_operations.log")
echo "⚠️ Emergency operations recorded: $emergency_count"
else
echo "✓ No emergency operations recorded"
fi
}
# Fleet deployment function
deploy_to_fleet() {
local policy="$1"
local fleet_file="$2"
if [[ ! -f "$fleet_file" ]]; then
log_action "ERROR: Fleet file not found: $fleet_file"
return 1
fi
log_action "Starting fleet deployment of policy: $policy"
while IFS= read -r host; do
[[ -z "$host" || "$host" =~ ^#.*$ ]] && continue
echo "Deploying to: $host"
# Copy this script to remote host and execute
ssh "$host" "bash -s" << EOF
#!/bin/bash
# Remote deployment of file protection policy: $policy
# Create directories
mkdir -p /etc/macfleet/{file_policies,reports,compliance,audit,backups}
# Apply the policy (simplified for remote execution)
$(declare -p FILE_CATEGORIES)
$(declare -p PROTECTION_LEVELS)
$(declare -p SECURITY_POLICIES)
$(type apply_security_policy | sed '1d')
apply_security_policy "$policy"
EOF
if [[ $? -eq 0 ]]; then
log_action "Successfully deployed to: $host"
else
log_action "Failed to deploy to: $host"
fi
done < "$fleet_file"
log_action "Fleet deployment completed"
}
# Main execution function
main() {
create_directories
case "${1:-}" in
"apply_policy")
apply_security_policy "$2"
;;
"protect_file")
protect_file "$2" "${3:-moderate}" "manual"
;;
"unprotect_file")
unprotect_file "$2" "${3:-manual_unlock}"
;;
"protect_category")
protect_category "$2" "${3:-moderate}" "manual"
;;
"bulk_protect")
bulk_protect_files "$2" "${3:-moderate}" "bulk_operation"
;;
"protect_system")
protect_system_files "${2:-standard}"
;;
"monitor_integrity")
monitor_file_integrity "${2:-standard}"
;;
"emergency_unlock")
emergency_unlock "${2:-manual}" "${3:-all}"
;;
"health_check")
perform_health_check
;;
"report")
generate_compliance_report "${2:-manual}"
;;
"deploy")
deploy_to_fleet "$2" "$3"
;;
"help"|*)
echo "MacFleet Enterprise File Protection System"
echo ""
echo "Usage: $0 <command> [options]"
echo ""
echo "Commands:"
echo " apply_policy <policy> - Apply security policy (enterprise_lockdown|standard_protection|development_safe|minimal_security|maintenance_mode)"
echo " protect_file <path> [level] - Protect single file (maximum|high|moderate|minimal|readonly)"
echo " unprotect_file <path> [reason] - Remove protection from file"
echo " protect_category <category> [level] - Protect file category (critical_apps|business_apps|system_configs|security_tools|development_tools|admin_utilities)"
echo " bulk_protect <file_list> [level] - Protect files from list"
echo " protect_system [mode] - Protect system files (lockdown|standard|minimal)"
echo " monitor_integrity [mode] - Monitor file integrity (standard|strict)"
echo " emergency_unlock <reason> [scope] - Emergency unlock (all|system|applications)"
echo " health_check - Perform system health check"
echo " report [policy] - Generate compliance report"
echo " deploy <policy> <fleet_file> - Deploy policy to fleet"
echo ""
echo "Examples:"
echo " $0 apply_policy enterprise_lockdown"
echo " $0 protect_file /Applications/Terminal.app maximum"
echo " $0 protect_system lockdown"
echo " $0 emergency_unlock 'maintenance required' system"
echo " $0 health_check"
;;
esac
}
# Execute main function
main "$@"
Protection Levels Explained
Level | Description | Flags Applied | Use Case |
---|---|---|---|
Maximum | Complete system protection | schg , uappnd , uchg | Critical system files |
High | Strong user protection | schg , uchg | Important applications |
Moderate | Standard user protection | uchg | Business applications |
Minimal | Basic append protection | uappnd | Log files, databases |
Read-only | Prevent modifications | ro | Configuration templates |
File Protection Use Cases
Protect Critical Applications
# Protect system utilities from modification
./file_protection.sh protect_category critical_apps maximum
# Protect business applications
./file_protection.sh protect_category business_apps high
System Configuration Protection
# Lock down system configuration files
./file_protection.sh protect_system lockdown
# Standard system protection
./file_protection.sh protect_system standard
Development Environment Safety
# Apply development-safe policy
./file_protection.sh apply_policy development_safe
# Protect development tools with minimal restrictions
./file_protection.sh protect_category development_tools minimal
Emergency Procedures
Complete System Unlock
# Emergency unlock all files
./file_protection.sh emergency_unlock "critical_maintenance" all
# Unlock only system files
./file_protection.sh emergency_unlock "system_update" system
File Recovery
# Check protection status
ls -lO /Applications/Terminal.app
# Remove specific protection
./file_protection.sh unprotect_file /Applications/Terminal.app "maintenance"
Important Security Considerations
- System Integrity Protection (SIP) provides additional layer beyond file flags
- Root privileges required for system immutable flags (
schg
) - Backup protection state before making changes
- Emergency procedures should be tested and documented
- File integrity monitoring helps detect unauthorized changes
Compliance and Auditing
The enterprise system provides comprehensive audit trails for:
- SOX Compliance - Financial data protection requirements
- HIPAA Requirements - Healthcare information security
- PCI-DSS Standards - Payment card industry data security
- NIST Framework - Cybersecurity standards compliance
- ISO 27001 - Information security management
Testing and Validation
Before enterprise deployment:
- Test protection levels on non-critical files first
- Verify emergency unlock procedures work correctly
- Confirm business applications remain functional
- Test integrity monitoring accuracy
- Validate compliance reporting completeness
This comprehensive system transforms basic file locking into an enterprise-grade protection platform with advanced security policies, compliance monitoring, and fleet management capabilities.