T09 · Insecure Skill Coding Practices
- Location
SKILL.md:63- Finding
Predictable Temporary Status File Permits Symlink-Based File Overwrite and Disclosure
- Content
View full analysis
Vulnerability Details
File Location:
SKILL.md, lines 63–79
Vulnerability Type: Predictable temporary file, symlink following, and time-of-check/time-of-use exposure
Risk Level: MediumVulnerable Code:
bash STATUS_FILE="/tmp/pilot-presence-status.txt" echo "online" > "$STATUS_FILE" while true; do status=$(cat "$STATUS_FILE" 2>/dev/null || echo "online") timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ) info=$(pilotctl --json info 2>/dev/null) hostname=$(echo "$info" | jq -r '.data.hostname // "unknown"') presence_payload=$(jq -n \ --arg hostname "$hostname" \ --arg status "$status" \ --arg timestamp "$timestamp" \ '{hostname: $hostname, status: $status, timestamp: $timestamp}') pilotctl --json publish "$COORDINATOR" "presence.status" --data "$presence_payload"Technical Analysis
The workflow stores its status in the fixed, globally predictable path
/tmp/pilot-presence-status.txt. It neither creates the file atomically with exclusive permissions nor verifies that the path is a regular file owned by the executing user. Standard shell redirection andcatfollow symbolic links.A local attacker can therefore pre-create the path as a symbolic link or replace it while the infinite loop is running. During initialization,
echo "online" > "$STATUS_FILE"follows the link and truncates or overwrites the target if the victim account can write to it. During later reads, a substituted link can point to another file readable by the victim. Its contents are assigned tostatus, safely encoded as JSON byjq, and then published to the configured coordinator. Safe JSON encoding prevents command injection but does not prevent information disclosure.The repeatedly executed loop also creates an ongoing time-of-check/time-of-use opportunity. No ownership, type, link-count, or permission checks are performed before reads.
Attack Path
- A local attac ...[truncated 1689 chars]
- Remediation
View remediation
Remediation Suggestions
- Avoid a shared, predictable file entirely when status can be maintained in process memory or obtained through authenticated IPC.
- If a file is required, create a private temporary directory atomically with
mktemp -d, set a restrictiveumask, and place the status file inside that directory. - Register a trap to remove the private directory on normal exit and common termination signals.
- Before every read, verify that the path is a regular file, is not a symbolic link, and is owned by the expected user.
- Use mechanisms that reject symbolic links, such as opening the file with
O_NOFOLLOWin a small trusted helper, because separate shell checks remain vulnerable to races. - Restrict file permissions to the owner, validate status against the allowed set (
online,busy,offline, andaway), and reject unexpected or oversized content before publication. - Avoid running this presence loop with elevated privileges.
A safer shell-level starting point is:
bash umask 077 STATUS_DIR=$(mktemp -d "${TMPDIR:-/tmp}/pilot-presence.XXXXXX") || exit 1 trap 'rm -rf -- "$STATUS_DIR"' EXIT HUP INT TERM STATUS_FILE="$STATUS_DIR/status" printf '%s\n' "online" > "$STATUS_FILE"This removes the globally predictable pathname. For robust race resistance, status-file reads should still be implemented using an atomic, no-follow file open rather than separate path checks.
