Back to skill

Security audit

Service Watchdog

Security checks for vulnerabilities and agentic risk

Overview

The skill is a real service monitor, but unsafe configuration handling could let a bad config run commands or damage writable files.

Install only after fixing or accepting these risks. Use this only with trusted watchdog.json files, avoid untrusted host, port, URL, and history_file values, prefer disabling history or confining it to a dedicated directory, and do not rely on HTTPS results for certificate trust until the insecure TLS behavior is corrected.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
watchdog.sh:238
Finding
Shell Command Injection Through the TCP Check Fallback<![CDATA[ ## Vulnerability Details **File Location**: `watchdog.sh`, lines 238-240 **Vulnerability Type**: OS command injection **Risk Level**: High ### Vulnerable Code ```bash else # Bash /dev/tcp fallback if timeout "$timeout_s" bash -c "echo >/dev/tcp/${host}/${port}" 2>/dev/null; then success=true fi fi ``` The values reaching the vulnerable operation are read from the configuration without validation: ```bash host=$(echo "$svc" | jq -r '.host') port=$(echo "$svc" | jq -r '.port') timeout_ms=$(echo "$svc" | jq -r ".timeout_ms // $DEFAULT_TIMEOUT_MS") result=$(check_tcp "$host" "$port" "$timeout_ms") ``` ### Technical Analysis The `host` and `port` values originate in `watchdog.json`. When neither `nc` nor `ncat` is installed, they are interpolated directly into a command string passed to `bash -c`. Quoting the overall argument to `bash -c` does not make the interpolated content safe. Shell metacharacters contained in either value become part of the command text parsed by the newly launched shell. An attacker capable of modifying or supplying the watchdog configuration can therefore introduce command substitutions, command separators, redirections, or pipelines. For example, a malicious host value containing shell syntax could cause an additional command to run when the fallback is reached. Exploitation depends on the absence of both `nc` and `ncat`, but the fallback is explicitly supported and documented by the Skill. ### Attack Path 1. An attacker gains the ability to create or modify the JSON configuration used by the Skill, including through a user-provided `--config` path or `WATCHDOG_CONFIG`. 2. The attacker defines a TCP service and inserts shell metacharacters into its `host` or `port` field. 3. The user or Agent invokes `watchdog.sh` as documented. 4. The target environment does not have `nc` or `ncat`, causing execution to enter the `/dev/tcp` fallback. 5. The untrusted values are interpolated into the string passed to ...[truncated 718 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not interpolate configuration data into a `bash -c` command. - Validate `port` using a strict integer check and require a value between 1 and 65535. - Validate `host` against an explicit hostname, IPv4, or IPv6 grammar before using it. - Prefer requiring a dedicated TCP client such as `nc` or `ncat`. - If `/dev/tcp` must be retained, pass values as positional parameters to an isolated shell and validate them before invocation rather than embedding them in command text. - Reject malformed configuration before processing any service. - Add regression tests containing shell metacharacters in `host` and `port` fields and verify that no command is executed. A safer design is to validate first and then pass values positionally: ```bash [[ "$port" =~ ^[0-9]+$ ]] && (( port >= 1 && port <= 65535 )) || { echo "fail|0|Invalid TCP port" return } [[ "$host" =~ ^[A-Za-z0-9._:-]+$ ]] || { echo "fail|0|Invalid TCP host" return } if timeout "$timeout_s" bash -c \ 'exec 3<>"/dev/tcp/$1/$2"' bash "$host" "$port" 2>/dev/null; then success=true fi ``` The hostname validation should be made more precise if internationalized names or IPv6 zone identifiers must be supported. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
watchdog.sh:119
Finding
Unrestricted History Path Allows Arbitrary Writable-File Corruption or Replacement<![CDATA[ ## Vulnerability Details **File Location**: `watchdog.sh`, lines 119-156 **Vulnerability Type**: Unrestricted file write and unsafe temporary-file handling **Risk Level**: High ### Vulnerable Code The history path is accepted directly from configuration: ```bash HISTORY_FILE=$(get_default "history_file" "$DEFAULT_HISTORY_FILE") ``` It is then used for file creation, append operations, and replacement without path confinement or symlink checks: ```bash init_history() { if [[ "$WRITE_HISTORY" == "true" ]] && [[ ! -f "$HISTORY_FILE" ]]; then echo "timestamp,name,type,status,response_ms,detail" > "$HISTORY_FILE" fi } write_history() { if [[ "$WRITE_HISTORY" == "true" ]]; then local name="$1" type="$2" status="$3" response_ms="$4" detail="$5" # CSV-escape fields with commas/quotes detail="${detail//\"/\"\"}" echo "${NOW_UTC},\"${name}\",${type},${status},${response_ms},\"${detail}\"" >> "$HISTORY_FILE" fi } prune_history() { if [[ "$WRITE_HISTORY" == "true" ]] && [[ -f "$HISTORY_FILE" ]]; then local cutoff_epoch=$((NOW_EPOCH - DEFAULT_HISTORY_RETENTION_DAYS * 86400)) local cutoff_date cutoff_date=$(date -u -d "@${cutoff_epoch}" '+%Y-%m-%d' 2>/dev/null || date -u -r "${cutoff_epoch}" '+%Y-%m-%d' 2>/dev/null || echo "") if [[ -n "$cutoff_date" ]]; then local tmp="${HISTORY_FILE}.tmp" head -1 "$HISTORY_FILE" > "$tmp" awk -F',' -v cutoff="$cutoff_date" 'NR>1 && $1 >= cutoff' "$HISTORY_FILE" >> "$tmp" mv "$tmp" "$HISTORY_FILE" fi fi } ``` ### Technical Analysis The `defaults.history_file` property can point outside the workspace, including to an absolute path. The script neither canonicalizes the path nor verifies that it remains within a dedicated history directory. Several unsafe operations follow: - `init_history` creates a configured path if it does not already exist. - `write_history` appends atta ...[truncated 2024 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Store history only under a dedicated directory controlled by the application. - Canonicalize the requested path and reject it unless it remains beneath the approved history directory. - Consider removing support for absolute history paths. - Reject symlinks and non-regular files for both the history file and its parent directory. - Create the history directory with restrictive permissions, such as `0700`, and history files with an appropriate `umask`, such as `077`. - Replace the predictable `.tmp` name with `mktemp` in the same trusted directory. - Write the replacement completely, apply appropriate permissions, and then perform an atomic rename. - Verify ownership and file type immediately before append and replacement operations. - If the configuration is not fully trusted, do not allow it to control output paths at all. For example, derive a fixed base directory and check the canonical parent: ```bash HISTORY_DIR="$WORKSPACE/.watchdog" mkdir -p -- "$HISTORY_DIR" chmod 700 -- "$HISTORY_DIR" requested_name=$(basename -- "$HISTORY_FILE") HISTORY_FILE="$HISTORY_DIR/$requested_name" [[ ! -L "$HISTORY_FILE" ]] || { echo "Error: History file must not be a symlink" >&2 exit 1 } tmp=$(mktemp "$HISTORY_DIR/.history.XXXXXX") trap 'rm -f -- "$tmp"' EXIT ``` The final implementation should also reject special files and ensure that the canonical history directory is not itself a symlink-controlled location. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
watchdog.sh:178
Finding
HTTPS Monitoring Disables Certificate Authentication<![CDATA[ ## Vulnerability Details **File Location**: `watchdog.sh`, lines 178-180 **Vulnerability Type**: Improper TLS certificate validation **Risk Level**: Medium ### Vulnerable Code ```bash local curl_args=(-s -S -o "$body_file" -D "$header_file" -w '%{http_code}' \ --max-time "$timeout_s" --connect-timeout "$timeout_s" \ -X "$method" -L --insecure) ``` The supplementary certificate checks also inspect certificate data without validating the trust chain or hostname: ```bash expiry_date=$(echo | openssl s_client -servername "$host" -connect "${host}:${port}" 2>/dev/null | \ openssl x509 -noout -enddate 2>/dev/null | sed 's/notAfter=//') ``` ```bash cert_info=$(echo | openssl s_client -servername "$host" -connect "${host}:${port}" 2>/dev/null | \ openssl x509 -noout -enddate -issuer -subject 2>/dev/null) ``` ### Technical Analysis The HTTP check always supplies `--insecure` to `curl`. For HTTPS URLs, this disables normal certificate-chain and hostname verification. The monitor will therefore accept self-signed certificates, certificates issued by an untrusted authority, and certificates issued for a different hostname. The separate OpenSSL logic does not compensate for this weakness. It extracts the presented certificate's expiry, issuer, and subject, but does not verify that the chain terminates in a trusted root or that the certificate identity matches the requested host. A forged certificate with a plausible validity period can consequently be reported as healthy. This contradicts the security purpose of HTTPS monitoring because endpoint reachability and certificate lifetime are checked without authenticating the endpoint's identity. ### Attack Path 1. An attacker obtains a network interception position or influences DNS or routing for a monitored HTTPS endpoint. 2. The attacker redirects the watchdog connection to an attacker-controlled TLS server. 3. The server presents any currently valid-looking certificate, including a self-s ...[truncated 1015 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove `--insecure` from the default `curl` arguments. - Rely on curl's default CA-chain and hostname validation for HTTPS services. - If private infrastructure requires a private CA, support an explicit CA bundle using `--cacert` rather than disabling verification. - If insecure TLS is operationally unavoidable, expose it as an explicit per-service opt-in, clearly label the result as insecure, and never treat it as fully healthy. - Validate OpenSSL connections with trust-chain and hostname checks where supported, for example with `-verify_return_error` and `-verify_hostname`. - Treat certificate verification failures as failed checks rather than merely reporting certificate expiry. - Add tests for self-signed, expired, hostname-mismatched, and untrusted-chain certificates. A secure curl configuration should omit the insecure option: ```bash local curl_args=(-s -S -o "$body_file" -D "$header_file" -w '%{http_code}' \ --max-time "$timeout_s" --connect-timeout "$timeout_s" \ -X "$method" -L) ``` For private certificate authorities, add a validated configuration property and pass it as an array element: ```bash [[ -n "$ca_file" ]] && curl_args+=(--cacert "$ca_file") ``` ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (3)

Missing User Warnings

Medium
Confidence
86% confidence
Finding
The skill explicitly instructs the agent to run active network checks, including cron-based repeated execution, but does not warn that this will initiate connections to configured internal or external hosts and may record sensitive infrastructure details such as hostnames, IPs, ports, and certificate metadata in outputs or logs. In an agent setting, that omission can cause operators to trigger scanning-like behavior or disclose internal topology without realizing the privacy and security implications.

Intent-Code Divergence

Medium
Confidence
88% confidence
Finding
The help text presents '--ssl-only' as a mode to check SSL certificates only, implying certificate-focused validation. However, the main HTTP check path uses curl with '--insecure' at L160-L162, so normal HTTPS monitoring explicitly skips TLS certificate verification even though the skill is framed as endpoint/SSL monitoring. This is an intent-level contradiction between the documented monitoring purpose and the code's actual trust behavior.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
The script invokes curl with --insecure for all HTTP/HTTPS checks, which disables TLS certificate validation and allows a man-in-the-middle attacker to spoof an HTTPS endpoint while still producing a successful health check. In a monitoring skill, this is especially dangerous because it can hide certificate misconfiguration, interception, or endpoint impersonation and give operators false assurance that a service is healthy.

Static analysis

No suspicious patterns detected.