Back to skill

Security audit

TinkerClaw Subagent Overseer

Security checks across malware telemetry and agentic risk

Overview

The skill appears purpose-aligned, but its local background monitor uses unsafe state-file and PID-file handling that deserves review before installation.

Review before installing. Use a narrow --workdir, prefer --no-filenames if filenames are sensitive, avoid --voice-files in shared spaces, do not run it as root, and only use a trusted private --status-dir until the state-directory and PID-file validation issues are fixed.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/overseer.sh:39
Finding
Predictable Runtime Directory and Symlink-Unsafe State Files<![CDATA[ ## Vulnerability Details **File Location**: `scripts/overseer.sh`, lines 39–49, 107–113, and 154–161 **Vulnerability Type**: Unsafe temporary directory and state-file handling **Risk Level**: High ### Vulnerable Code ```bash default_status_dir() { if [[ -n "${XDG_RUNTIME_DIR:-}" && -d "${XDG_RUNTIME_DIR}" ]]; then echo "${XDG_RUNTIME_DIR}/overseer" else echo "/tmp/overseer-$(id -u)" fi } STATUS_DIR="${OVERSEER_DIR:-$(default_status_dir)}" ``` ```bash LOGFILE="$STATUS_DIR/overseer.log" STATUS_FILE="$STATUS_DIR/status.json" LOCKFILE="$STATUS_DIR/overseer.lock" PIDFILE="$STATUS_DIR/overseer.pid" MARKER="$STATUS_DIR/fs-marker" mkdir -p "$STATUS_DIR" chmod 700 "$STATUS_DIR" 2>/dev/null || true ``` ```bash exec 200>"$LOCKFILE" if ! flock -n 200; then echo "Another overseer is already running. Exiting." >&2 exit 0 fi echo $$ > "$PIDFILE" ``` ### Technical Analysis When `XDG_RUNTIME_DIR` is unavailable, the script falls back to the predictable path `/tmp/overseer-<UID>`. It calls `mkdir -p` without checking whether the path already existed, whether it is a real directory rather than a symlink, or whether it is owned by the current user. The subsequent `chmod` failure is explicitly ignored: ```bash chmod 700 "$STATUS_DIR" 2>/dev/null || true ``` State files are then opened by predictable names using operations that follow symbolic links. For example, `exec 200>"$LOCKFILE"` and `echo $$ > "$PIDFILE"` can truncate or overwrite the targets of symlinks. On a system where an attacker can pre-create the fallback directory with suitable permissions, and where operating-system symlink protections do not block the operation, this creates a local symlink attack. The same risk applies when an unsafe attacker-influenced directory is supplied through `--status-dir` or `OVERSEER_DIR`. ### Attack Path 1. Determine the victim user's UID and predict `/tmp/overseer-<UID>`. 2. Before the victim starts Overseer, create that direc ...[truncated 1272 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer an existing, ownership-validated `XDG_RUNTIME_DIR`. 2. If a fallback is necessary, create it atomically with a non-predictable name and restrictive permissions: ```bash umask 077 STATUS_DIR="$(mktemp -d "/tmp/overseer-$(id -u).XXXXXXXX")" || exit 1 ``` 3. If a stable path is required, use `lstat` or `stat` to verify that the path: - Is a real directory and not a symlink. - Is owned by the effective UID. - Is not writable by group or other users. 4. Fail closed if directory creation, ownership validation, or permission changes fail. 5. Validate custom `--status-dir` and `OVERSEER_DIR` paths using the same rules. 6. Create state files without following symbolic links, using a helper that supports `O_NOFOLLOW` and exclusive creation where appropriate. 7. Set `umask 077` before creating any status, PID, lock, marker, log, or temporary files. 8. Add tests covering pre-existing directories, incorrect ownership, permissive modes, and symlinked state filenames. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/overseer.sh:121
Finding
Forged or Stale PID File Can Terminate an Unrelated Process<![CDATA[ ## Vulnerability Details **File Location**: `scripts/overseer.sh`, lines 121–140 **Vulnerability Type**: Unauthenticated PID-file process control **Risk Level**: Medium ### Vulnerable Code ```bash stop_running() { if [[ ! -f "$PIDFILE" ]]; then echo "No overseer pid file in $STATUS_DIR — nothing to stop." return 1 fi local pid pid="$(cat "$PIDFILE" 2>/dev/null || echo "")" if [[ -z "$pid" || ! -d "/proc/$pid" ]]; then echo "Overseer is not running (stale pid file removed)." rm -f "$PIDFILE" return 1 fi kill "$pid" 2>/dev/null || true for _ in $(seq 1 50); do [[ -d "/proc/$pid" ]] || break sleep 0.1 done if [[ -d "/proc/$pid" ]]; then echo "Overseer (pid $pid) did not stop on SIGTERM; sending SIGKILL." kill -9 "$pid" 2>/dev/null || true fi rm -f "$PIDFILE" echo "Overseer stopped (pid $pid)." return 0 } ``` ### Technical Analysis The `--stop` and `--cleanup` operations trust the contents of `overseer.pid`. The only process identity check is whether `/proc/<pid>` currently exists. The script does not verify: - That the PID contains only a valid positive decimal integer. - That the process belongs to the expected user. - That the process command line corresponds to `overseer.sh`. - That the process was launched using the expected status directory. - That the process start time matches the process that created the PID file. - That the PID file itself is trusted and owned by the current user. PID values are reused by the operating system. Consequently, even a PID file originally written by Overseer can eventually refer to an unrelated process. A forged PID file can produce the same result immediately. After sending SIGTERM, the script may escalate to SIGKILL solely because a process with that PID still exists. It does not revalidate process identity before doing so. ### Attack Path 1. Identify a target process running un ...[truncated 1293 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject any PID that is not a positive decimal integer: ```bash [[ "$pid" =~ ^[1-9][0-9]*$ ]] || { echo "Invalid PID file." >&2 return 1 } ``` 2. Verify `/proc/<pid>/status` reports the expected effective UID. 3. Verify `/proc/<pid>/cmdline` identifies the expected `overseer.sh` instance and status directory. 4. Record the process start time from `/proc/<pid>/stat` when creating the PID file and compare it before sending a signal. This prevents PID-reuse attacks. 5. Store a random instance token in both process state and the state directory, and validate it during stop operations. 6. Revalidate all identity attributes immediately before escalating from SIGTERM to SIGKILL. 7. Refuse to use PID files that are symlinks, not regular files, incorrectly owned, or writable by other users. 8. Prefer a supervised service manager or a locked control socket over an unauthenticated PID file. 9. Add tests with forged PIDs, stale PIDs, malformed PID values, and PID reuse simulations. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/overseer.sh:55
Finding
Unvalidated Numeric Options Can Crash the Daemon or Corrupt Status JSON<![CDATA[ ## Vulnerability Details **File Location**: `scripts/overseer.sh`, lines 55–59, 284–293, 304–311, and 354 **Vulnerability Type**: Improper input validation **Risk Level**: Low ### Vulnerable Code ```bash while [[ $# -gt 0 ]]; do case "$1" in --interval) INTERVAL="$2"; shift 2 ;; --workdir) WORKDIR="$2"; shift 2 ;; --labels) LABELS="$2"; shift 2 ;; --max-stale) MAX_STALE="$2"; shift 2 ;; ``` ```bash new_stale=$((prev_stale + 1)) set_stale "$label" "$new_stale" local_status="idle" if [[ "$new_stale" -ge "$MAX_STALE" ]]; then local_status="stuck" elif [[ "$new_stale" -ge 2 ]]; then local_status="warning" fi ``` ```bash cat > "$TMP_STATUS" <<-STATUSEOF { "timestamp": "$NOW", "cycle": $CYCLE, "interval_sec": $INTERVAL, "gateway": { "pid": ${GW_PID:-null}, "health": $GW_HEALTH }, "subagents": { "count": $SUBAGENT_COUNT, "details": [$LABEL_STATUS] }, "filesystem": { "changes_since_last": $CHANGED, "recent_files": "$(json_escape "$RECENT")", "filenames_recorded": $RECORD_FILENAMES }, "max_stale_threshold": $MAX_STALE } STATUSEOF ``` ```bash sleep "$INTERVAL" ``` ### Technical Analysis The `--interval` and `--max-stale` options are accepted as arbitrary strings. They are subsequently used in three contexts that require validated numeric data: 1. `MAX_STALE` is interpreted as an arithmetic operand. 2. Both values are inserted directly into JSON without quoting or encoding. 3. `INTERVAL` is passed to `sleep`. Malformed values can therefore cause arithmetic errors, invalid `sleep` invocations, or syntactically invalid status JSON. Because the daemon uses `set -euo pipefail`, many such errors terminate monitoring entirely. Negative, zero, or extremely large values also create undesirable behavior even where they are syntactically accepted. For example, a zero interval can cause a resource-intensive tight loop, while an excessive interval can effectively disable timely monitoring. ...[truncated 1289 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Validate both options immediately during argument parsing. 2. Require canonical positive decimal integers: ```bash require_positive_integer() { local name="$1" local value="$2" if [[ ! "$value" =~ ^[1-9][0-9]*$ ]]; then printf 'Invalid %s: expected a positive integer\n' "$name" >&2 exit 2 fi } ``` 3. Apply explicit upper bounds appropriate for the monitoring function, for example: - `INTERVAL`: 1 through 86,400 seconds. - `MAX_STALE`: 1 through a documented operational maximum. 4. Reject missing option operands before referencing `$2`. 5. Generate JSON with a real JSON encoder, such as Python or `jq`, instead of raw here-document interpolation. 6. Validate the complete generated JSON before atomically replacing the current status file. 7. Add tests for missing, zero, negative, nonnumeric, fractional, and excessively large values. ]]>
Vulnerability Patterns
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (1)

Tp4

High
Category
MCP Tool Poisoning
Confidence
85% confidence
Finding
The marketing claim says 'pure OS-level process checks' and 'no polling loops,' but the documented behavior includes a periodic daemon loop, OpenClaw CLI calls, filesystem scanning, persistent local state, and optional voice output. This mismatch can cause users to grant trust or deploy the skill under a false understanding of its data access and runtime behavior, increasing the chance of unintended privacy exposure through filenames, local persistence, or audible disclosure.

VirusTotal

VirusTotal findings are pending for this skill version.

View on VirusTotal

Static analysis

No suspicious patterns detected.