T09 · Insecure Skill Coding Practices
- Location
scripts/healthcheck.sh:4- Finding
Predictable Temporary Paths Allow Symlink-Based File Overwrite
- Content
View full analysis
Vulnerability Details
File Location:
scripts/healthcheck.sh, lines 4-12 and 35-54
Vulnerability Type: Unsafe temporary-file handling and symbolic-link following
Risk Level: MediumVulnerable Code
bash OUT_ROOT="${HEALTHCHECK_OUTPUT_DIR:-/tmp/openclaw-healthcheck}" EXCLUDE_RAW="${HEALTHCHECK_EXCLUDE:-small model,sandbox,groupPolicy}" TS="$(date +%H%M%S)" DAY="$(date +%F)" OUT_DIR="${OUT_ROOT}/${DAY}/${TS}" mkdir -p "$OUT_DIR" LOG="$OUT_DIR/run.log" SUMMARY_JSON="$OUT_DIR/summary.json"The files are subsequently opened using ordinary shell redirections:
bash { echo "[healthcheck] started: $(date -Is)" echo "[healthcheck] out_dir: $OUT_DIR" echo "[healthcheck] exclude: $EXCLUDE_RAW" } > "$LOG" cat > "$SUMMARY_JSON" <<EOF { "verdict": "$verdict", "passed": $passed, "warn": $warn, "fail": $fail, "artifact_path": "$OUT_DIR", "issues": [$(printf '"%s",' "${issues[@]:-}" | sed 's/,$//')] } EOFTechnical Analysis
The default output root is a fixed path under the shared
/tmpnamespace. Subdirectories are derived only from the current date and second, making the final paths predictable, especially because the accompanying cron example specifies known execution times.mkdir -pdoes not establish exclusive ownership of an already existing directory and can traverse attacker-created directory structures. The later> "$LOG"and> "$SUMMARY_JSON"redirections follow symbolic links and truncate their targets. The script does not verify directory ownership, reject symbolic links, use an atomically created private directory, or open files with no-follow semantics.Consequently, another local user who can write under
/tmpmay pre-create the expected directory and placerun.logorsummary.jsonsymbolic links pointing to files writable by the account running the health check.Attack Path
- A local attacke ...[truncated 1725 chars]
- Remediation
View remediation
Remediation Suggestions
-
Create the output root as a private directory with restrictive permissions before use:
bash umask 077 OUT_ROOT="${HEALTHCHECK_OUTPUT_DIR:-${XDG_STATE_HOME:-$HOME/.local/state}/openclaw-healthcheck}" install -d -m 700 -- "$OUT_ROOT" -
Prefer a trusted per-user state directory instead of a shared
/tmplocation. -
If temporary storage is required, atomically create an unpredictable directory with
mktemp -d:bash umask 077 BASE="${HEALTHCHECK_OUTPUT_DIR:-/tmp/openclaw-healthcheck}" install -d -m 700 -- "$BASE" OUT_DIR="$(mktemp -d "$BASE/healthcheck.XXXXXXXX")" -
Before writing, verify that the output directory is owned by the effective user and is not a symbolic link.
-
Create output files atomically and reject existing paths. Where available, use a helper that opens files with
O_CREAT | O_EXCL | O_NOFOLLOW. -
Run the scheduled job with a dedicated, minimally privileged account so that any filesystem overwrite is limited in scope.
-
Consider retaining date-based organization only after first creating a private, unpredictable directory; do not rely on timestamps as a security boundary.
-
