T09 · Insecure Skill Coding Practices
- Location
scripts/gateway-watchdog.sh:17- Finding
Predictable Shared Temporary File Permits Symlink-Based File Overwrite
- Content
View full analysis
Vulnerability Details
File Location:
scripts/gateway-watchdog.sh, lines 17 and 67–78
Vulnerability Type: Unsafe temporary file handling
Risk Level: MediumVulnerable Code
bash COOLDOWN_FILE="/tmp/openclaw-gateway-restart-cooldown"bash is_in_cooldown() { if [ -f "$COOLDOWN_FILE" ]; then local cooldown_time cooldown_time=$(cat "$COOLDOWN_FILE" 2>/dev/null) local now now=$(date "+%s") if [ -n "$cooldown_time" ] && [ $(( now - cooldown_time )) -lt $COOLDOWN_SECONDS ]; then return 0 # still in cooldown fi fi return 1 # not in cooldown } set_cooldown() { date "+%s" > "$COOLDOWN_FILE" }Technical Analysis
The watchdog stores its restart cooldown state at a fixed, predictable path in the shared
/tmpdirectory. The file is neither securely created nor checked for symbolic links before shell redirection writes to it.A local attacker who can write to
/tmpcan pre-create/tmp/openclaw-gateway-restart-cooldownas a symbolic link. Whenset_cooldownexecutes, the>redirection follows that link and truncates the linked target before writing an epoch timestamp.The service is documented as a per-user LaunchAgent, so the write occurs with the privileges of the user running the watchdog. This limits the target to files writable by that user and does not independently provide root privilege escalation.
Attack Path
- A local attacker identifies a file writable by the watchdog user.
- The attacker creates
/tmp/openclaw-gateway-restart-cooldownas a symbolic link to that target. - The gateway becomes unavailable, or the attacker causes its loopback health check to fail.
- The watchdog exhausts its HTTP and port retries.
- If the cooldown check does not prevent a restart,
restart_gatewayinvokesset_cooldown. - Shell redirection follows the attacker-controlled sym ...[truncated 695 chars]
- Remediation
View remediation
Remediation Suggestions
- Move the cooldown state out of shared
/tmpand into a private per-user directory, such as$HOME/.openclaw/run. - Create that directory with mode
0700and the state file with mode0600. - Reject symbolic links and unexpected file types before reading state.
- Write state to a securely created temporary file within the private directory and atomically rename it into place.
- Validate cooldown content as an unsigned integer before arithmetic evaluation.
- Use a per-user path if temporary storage remains necessary, and ensure ownership and permissions are verified.
Example hardened approach:
bash STATE_DIR="$HOME/.openclaw/run" COOLDOWN_FILE="$STATE_DIR/gateway-restart-cooldown" mkdir -p "$STATE_DIR" chmod 700 "$STATE_DIR" set_cooldown() { local temporary_file temporary_file=$(mktemp "$STATE_DIR/.cooldown.XXXXXX") || return 1 chmod 600 "$temporary_file" date "+%s" > "$temporary_file" || { rm -f "$temporary_file" return 1 } mv -f "$temporary_file" "$COOLDOWN_FILE" }- Move the cooldown state out of shared
