Skip to content

Commit c221def

Browse files
add guards for rmasync
1 parent e193fc5 commit c221def

1 file changed

Lines changed: 52 additions & 14 deletions

File tree

src/platform/mac/rmAsync.zsh

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
# Instantly move matched globs to staging, delete async in background.
77
# Perfect for millions of files (node_modules, build artifacts, etc).
88
#
9+
# SECURITY: This script includes protections against:
10+
# - Directory traversal attacks (.. rejection, symlink resolution)
11+
# - Critical system path deletion (/, /bin, /usr, /etc, etc)
12+
# - Staging folder escape (only deletes within $HOME/.rmAsync)
13+
# - Race conditions (atomic operations, path validation)
14+
# - Privilege escalation (uid/gid checks, file permissions)
15+
#
916
# Usage:
1017
# rmAsync <pattern> [<pattern2> ...]
1118
# rmAsync status
@@ -16,26 +23,24 @@
1623
# - Each execution gets unique staging folder (UUID + timestamp)
1724
# - All matches staged atomically, then deleted as single folder
1825
#
19-
# Behavior:
20-
# 1. Expand all glob patterns to file list
21-
# 2. Create unique staging folder: $HOME/.rmAsync/<uuid>_<timestamp>
22-
# 3. Move all matched files into staging folder (instant on same filesystem)
23-
# 4. Return immediately (original paths now available)
24-
# 5. Delete staging folder in background
25-
# 6. Auto-cleanup orphans if no other rm processes active
26-
#
27-
# Performance:
28-
# - Staging: O(n) where n = file count (fast due to inode moves)
29-
# - Main process: returns immediately after staging
30-
# - Deletion: happens in background, non-blocking
31-
#
3226
# ==============================================================================
3327

3428
set -o pipefail
3529

30+
# Strict umask prevents world-readable staging dirs
31+
umask 0077
32+
3633
# Configuration
3734
readonly RM_ASYNC_DIR="${HOME}/.rmAsync"
3835
readonly RM_ASYNC_PID_FILE="${RM_ASYNC_DIR}/.pids"
36+
readonly RM_ASYNC_LOCK="${RM_ASYNC_DIR}/.lock"
37+
38+
# Security: Verify we're running as the user who owns HOME
39+
readonly EXPECTED_UID=$(stat -f%u "$HOME" 2>/dev/null || stat -c%u "$HOME" 2>/dev/null)
40+
if [[ $(id -u) != "$EXPECTED_UID" ]]; then
41+
echo "Error: HOME ownership mismatch. Possible privilege escalation attempt."
42+
exit 1
43+
fi
3944

4045
# Colors for output
4146
readonly RED='\033[0;31m'
@@ -78,11 +83,31 @@ _generate_unique_id() {
7883
}
7984

8085
_init_staging_dir() {
86+
# Ensure staging dir doesn't exist as symlink (symlink attack prevention)
87+
if [[ -L "$RM_ASYNC_DIR" ]]; then
88+
_log_error "Staging directory is a symlink (potential attack): $RM_ASYNC_DIR"
89+
return 1
90+
fi
91+
8192
mkdir -p "$RM_ASYNC_DIR" || {
8293
_log_error "Failed to create staging directory: $RM_ASYNC_DIR"
8394
return 1
8495
}
85-
chmod 700 "$RM_ASYNC_DIR" 2>/dev/null
96+
97+
# Verify directory ownership (must be current user)
98+
local dir_owner
99+
dir_owner=$(stat -f%u "$RM_ASYNC_DIR" 2>/dev/null || stat -c%u "$RM_ASYNC_DIR" 2>/dev/null)
100+
101+
if [[ "$dir_owner" != "$(id -u)" ]]; then
102+
_log_error "Staging directory owned by different user: $RM_ASYNC_DIR"
103+
return 1
104+
fi
105+
106+
# Set strict permissions (700 = rwx------)
107+
chmod 700 "$RM_ASYNC_DIR" 2>/dev/null || {
108+
_log_error "Failed to set permissions on staging directory"
109+
return 1
110+
}
86111
}
87112

88113
_validate_critical_paths() {
@@ -94,6 +119,13 @@ _validate_critical_paths() {
94119
return 1
95120
fi
96121

122+
# SECURITY: Reject if path is a symlink (symlink attack prevention)
123+
# We want to validate the link itself, not its target
124+
if [[ -L "$path" ]]; then
125+
_log_error "Path is a symlink (potential attack): $path"
126+
return 1
127+
fi
128+
97129
# Resolve to absolute path to prevent tricks
98130
local abs_path
99131
abs_path=$(cd "$(dirname "$path")" 2>/dev/null && pwd) || {
@@ -120,6 +152,12 @@ _validate_critical_paths() {
120152
return 1
121153
fi
122154

155+
# Safety: reject if path contains null bytes (shell injection attempt)
156+
if [[ "$abs_path" == *$'\0'* ]]; then
157+
_log_error "Invalid path (contains null bytes): $path"
158+
return 1
159+
fi
160+
123161
return 0
124162
}
125163

0 commit comments

Comments
 (0)