Skip to content

Commit fd744e0

Browse files
CopilotPDowney
andauthored
security: harden run-install-step.sh against directory traversal and missing sudo
Agent-Logs-Url: https://github.com/EngineScript/EngineScript/sessions/b7a76c75-34d2-4d2c-b506-bda5f1f115b4 Co-authored-by: PDowney <[email protected]>
1 parent b4c258d commit fd744e0

2 files changed

Lines changed: 64 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ Changes are organized by date, with the most recent changes listed first.
66

77
## 2026-04-10
88

9+
### 🔒 CI SECURITY HARDENING: run-install-step.sh
10+
11+
- Added install script path validation using `realpath` to prevent directory traversal attacks; the resolved canonical path must be within `scripts/ci/` and is used in place of the raw input when invoking `bash`.
12+
- Added log path validation to restrict log files to the current working directory, block symlinks, and reject non-regular-file targets before `touch` is called.
13+
- Added a pre-flight check for `sudo` availability and non-interactive sudo privileges before attempting any privileged commands, producing a clear error message if either requirement is not met.
14+
15+
## 2026-04-10
16+
917
### 🐛 VHOST IMPORT LOGGING / EXTRACTION FLOW FIXES
1018

1119
- Removed a duplicate WordPress extraction block in `scripts/functions/vhost/vhost-import.sh` that re-ran archive extraction and wp-config path detection after those steps had already completed.

scripts/ci/run-install-step.sh

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ TIMEOUT_SECONDS="$2"
1212
INSTALL_SCRIPT_PATH="$3"
1313
LOG_PATH="$4"
1414
INTEGER_REGEX='^[0-9]+$'
15+
ALLOWED_INSTALL_DIR="$(realpath "$(pwd)/scripts/ci")"
16+
CANONICAL_INSTALL_SCRIPT_PATH=""
1517
# `timeout` returns 124 when the wrapped command times out.
1618
TIMEOUT_EXIT_CODE=124
1719

@@ -25,18 +27,71 @@ if [ ! -f "$INSTALL_SCRIPT_PATH" ]; then
2527
exit 1
2628
fi
2729

30+
if ! CANONICAL_INSTALL_SCRIPT_PATH="$(realpath "$INSTALL_SCRIPT_PATH" 2>/dev/null)"; then
31+
echo "Error: unable to resolve install script path: $INSTALL_SCRIPT_PATH" >&2
32+
exit 1
33+
fi
34+
35+
case "$CANONICAL_INSTALL_SCRIPT_PATH" in
36+
"$ALLOWED_INSTALL_DIR"/*) ;;
37+
*)
38+
echo "Error: install script path must be within $ALLOWED_INSTALL_DIR" >&2
39+
exit 1
40+
;;
41+
esac
42+
43+
ALLOWED_LOG_BASE_DIR="$(pwd -P)"
44+
LOG_PARENT_DIR="$(dirname -- "$LOG_PATH")"
45+
LOG_FILENAME="$(basename -- "$LOG_PATH")"
46+
RESOLVED_LOG_PARENT="$(cd "$LOG_PARENT_DIR" 2>/dev/null && pwd -P)"
47+
48+
if [ -z "${RESOLVED_LOG_PARENT:-}" ]; then
49+
echo "Error: log directory does not exist or is not accessible: $LOG_PARENT_DIR" >&2
50+
exit 1
51+
fi
52+
53+
if [ "$RESOLVED_LOG_PARENT" != "$ALLOWED_LOG_BASE_DIR" ] && [[ "$RESOLVED_LOG_PARENT"/ != "$ALLOWED_LOG_BASE_DIR"/* ]]; then
54+
echo "Error: log path must be within $ALLOWED_LOG_BASE_DIR: $LOG_PATH" >&2
55+
exit 1
56+
fi
57+
58+
if [ "$LOG_FILENAME" = "." ] || [ "$LOG_FILENAME" = ".." ]; then
59+
echo "Error: invalid log file name: $LOG_PATH" >&2
60+
exit 1
61+
fi
62+
63+
if [ -L "$LOG_PATH" ]; then
64+
echo "Error: log path must not be a symlink: $LOG_PATH" >&2
65+
exit 1
66+
fi
67+
68+
if [ -e "$LOG_PATH" ] && [ ! -f "$LOG_PATH" ]; then
69+
echo "Error: log path must be a regular file: $LOG_PATH" >&2
70+
exit 1
71+
fi
72+
2873
if ! touch "$LOG_PATH" 2>/dev/null; then
2974
echo "Error: log file is not writable: $LOG_PATH" >&2
3075
exit 1
3176
fi
3277

78+
if ! command -v sudo >/dev/null 2>&1; then
79+
echo "Error: sudo is required but not available in PATH" >&2
80+
exit 1
81+
fi
82+
83+
if ! sudo -n true >/dev/null 2>&1; then
84+
echo "Error: sudo privileges are required to run installation steps non-interactively" >&2
85+
exit 1
86+
fi
87+
3388
echo "Installing ${COMPONENT_NAME}..."
3489
echo "Script start time: $(date)" > "$LOG_PATH"
3590

3691
set +e
3792
timeout "$TIMEOUT_SECONDS" \
3893
sudo env CI_ENVIRONMENT=true DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a NEEDRESTART_SUSPEND=1 \
39-
bash "$INSTALL_SCRIPT_PATH" 2>&1 | tee -a "$LOG_PATH"
94+
bash "$CANONICAL_INSTALL_SCRIPT_PATH" 2>&1 | tee -a "$LOG_PATH"
4095
PIPE_EXIT_CODES=("${PIPESTATUS[@]}")
4196
SCRIPT_EXIT_CODE="${PIPE_EXIT_CODES[0]}"
4297
TEE_EXIT_CODE="${PIPE_EXIT_CODES[1]}"

0 commit comments

Comments
 (0)