T09 · Insecure Skill Coding Practices
- Location
lib/checkers.py:218- Finding
Arbitrary Local Command Execution Through Shell Injection in Health Check Targets
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This monitoring skill is coherent, but it needs review because saved service targets can be executed through unsafe shell commands and alert data can be sent to Telegram despite inconsistent disclosure.
Review before installing. Only use this under a low-privilege account, do not add service targets from untrusted sources, avoid enabling the systemd timer until shell command construction is fixed, and be careful with ssh_cmd because it runs commands on remote systems. Treat Telegram bot tokens as secrets and assume Telegram notifications may send service names, targets, and health details off-host.
lib/checkers.py:218Arbitrary Local Command Execution Through Shell Injection in Health Check Targets
nex-healthcheck.py:386Telegram Bot Token Material Disclosed by Configuration Command
The skill includes command-execution-based monitoring paths that exceed simple passive health checks by constructing shell commands for Docker inspection and delegating to remote SSH execution. In a monitoring skill, this is more dangerous because operators may trust configuration inputs and deploy it with elevated privileges on production infrastructure.
This is a classic tool-parameter-abuse case: untrusted container_name is embedded in a shell command and executed with shell=True. An attacker who can influence service configuration can supply shell metacharacters to run arbitrary commands, turning a health check into code execution on the host.
if host and ssh_user:
output, code = _run_ssh_command(host, ssh_user, cmd)
else:
output = subprocess.check_output(cmd, shell=True, timeout=10, text=True).strip()
code = 0
if code == 0 and output in ["running", "paused"]:
This command allows tool-parameter abuse because service_name is inserted directly into a shell command executed with shell=True. A maliciously crafted service name can append additional commands or alter command semantics, leading to arbitrary command execution.
output, code = _run_ssh_command(host, ssh_user, cmd)
is_active = code == 0
else:
output = subprocess.check_output(cmd, shell=True, timeout=5, text=True).strip()
is_active = True
if is_active:
The disk check exposes shell injection through the path parameter because it is concatenated into a shell command string with a pipe and run with shell=True. Any attacker-controlled path can abuse shell metacharacters to execute arbitrary commands on the monitored host.
if host and ssh_user:
output, code = _run_ssh_command(host, ssh_user, cmd)
else:
output = subprocess.check_output(cmd, shell=True, timeout=10, text=True).strip()
code = 0
if code != 0 or not output:
This helper is the most severe issue in the file because it combines unrestricted parameters (user, host, command) with shell string interpolation and shell=True. An attacker can exploit quoting to execute arbitrary local commands before SSH runs, while also sending arbitrary commands to remote hosts, making compromise of both the monitoring server and managed infrastructure possible.
"""Helper: Run command via SSH. Returns (output, exit_code)."""
ssh_cmd = f"ssh {user}@{host} '{command}'"
try:
output = subprocess.check_output(ssh_cmd, shell=True, timeout=timeout, text=True, stderr=subprocess.STDOUT)
return output.strip(), 0
except subprocess.CalledProcessError as e:
return e.output or "", e.returncode
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.
This will:
1. Create `~/.nex-healthcheck/` data directory
2. Create SQLite database for checks and incidents
3. Create `~/.local/bin/nex-healthcheck` wrapper script
4. (Linux only) Install optional systemd timer for periodic checks
The skill advertises capabilities that clearly require shell, network, and environment-variable access, but it does not declare any explicit tool scope or permission boundaries. This increases the chance an agent can invoke the skill with broader-than-necessary privileges, enabling command execution, outbound connections, and secret access without transparent constraints.
The trigger phrases are broad and overlap with ordinary health/status questions, making unintended activation more likely. In this skill, accidental invocation is more dangerous because the documented actions include local setup, state changes, remote SSH checks, and network probing rather than read-only summarization.
The skill explicitly supports an ssh_cmd mode that runs arbitrary commands via SSH, which is effectively unrestricted remote command execution. In the context of an agent skill, this is especially dangerous because loosely scoped user input or prompt injection could be turned into execution on remote infrastructure, leading to system compromise, data theft, or destructive actions.
- `ssl_cert`: SSL certificate expiry check
- `docker`: Docker container status (requires docker CLI or SSH)
- `systemd`: Systemd service status (requires systemctl or SSH)
- `ssh_cmd`: Run arbitrary command via SSH
- `ping`: ICMP ping check
- `disk`: Disk usage check
The documentation states that no data is sent externally, yet the same skill supports Telegram notifications using externally provided bot credentials. This mismatch can mislead users and downstream agents about data egress risk, causing monitoring results or infrastructure metadata to be transmitted off-host unexpectedly.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
return False
try:
url = f"https://api.telegram.org/bot{token}/sendMessage"
data = urllib.parse.urlencode({
"chat_id": chat_id,
"text": message,
This code constructs a Telegram API request and transmits the alert message plus chat identifier to an external service. Although the function docstring says it sends alerts via Telegram, there is no user-facing warning, confirmation, or explicit disclosure that health-check results and incident details will be sent off-host to Telegram.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
cmd.append(domain)
try:
output = subprocess.check_output(cmd, timeout=5, text=True).strip()
except FileNotFoundError:
# Fall back to nslookup
cmd = ["nslookup", domain]
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
cmd.append(domain)
try:
output = subprocess.check_output(cmd, timeout=5, text=True).strip()
except FileNotFoundError:
# Fall back to nslookup
cmd = ["nslookup", domain]
The Docker check constructs docker inspect ... {container_name} as a string and executes it locally with shell=True. If container_name is user-controlled, shell metacharacters can inject arbitrary commands on the local host; when used remotely, the same unsanitized string is sent through the vulnerable SSH helper, compounding the risk.
if host and ssh_user:
output, code = _run_ssh_command(host, ssh_user, cmd)
else:
output = subprocess.check_output(cmd, shell=True, timeout=10, text=True).strip()
code = 0
if code == 0 and output in ["running", "paused"]:
The systemd check builds systemctl is-active {service_name} and runs it with shell=True, allowing a crafted service name to inject arbitrary shell syntax locally. If executed through the SSH helper, the unsanitized command also becomes a remote-command injection path with possible local shell injection in the SSH wrapper itself.
output, code = _run_ssh_command(host, ssh_user, cmd)
is_active = code == 0
else:
output = subprocess.check_output(cmd, shell=True, timeout=5, text=True).strip()
is_active = True
if is_active:
This code exposes a function that runs an arbitrary command via SSH and returns its result, but the only inline description is a terse docstring and there is no confirmation prompt, visible warning, or user-facing disclosure about remote command execution. Because this operation can change remote system state or expose remote output, it meets the code-file criteria for a safety-critical subprocess/shell operation lacking warning.
check_ssh_command is explicitly designed to run arbitrary commands supplied through configuration on remote systems. In the context of an agent skill for monitoring, unrestricted tool access is highly dangerous because it can be abused to execute destructive commands on infrastructure and, due to the unsafe SSH wrapper, potentially also trigger local command injection on the monitoring host.
def check_ssh_command(host, user, command, expected_output=None):
"""Run arbitrary command via SSH."""
try:
start = time.time()
output, code = _run_ssh_command(host, user, command)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
try:
start = time.time()
cmd = ["ping", "-c", str(count), "-W", "5", host]
output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, timeout=20, text=True)
response_time_ms = int((time.time() - start) * 1000)
# Parse packet loss
The manifest describes website/API/SSL/DNS/Docker/systemd/network/SSH health checks, but it does not mention disk-capacity or filesystem-usage monitoring. This file adds a separate check_disk capability and wires it into the dispatcher, extending the skill beyond the stated health-check scope presented to users.
The disk check interpolates path into df {path} | tail -1 and executes it with shell=True. A malicious path containing shell metacharacters can execute arbitrary local commands; the pipeline also forces shell usage unnecessarily, increasing the attack surface for a monitoring function.
if host and ssh_user:
output, code = _run_ssh_command(host, ssh_user, cmd)
else:
output = subprocess.check_output(cmd, shell=True, timeout=10, text=True).strip()
code = 0
if code != 0 or not output:
The SSH helper builds a shell command string with user, host, and command interpolated directly into ssh {user}@{host} '{command}' and executes it with shell=True. Any attacker-controlled value in user, host, or especially command can break quoting and inject arbitrary local shell commands on the machine running the health checker, in addition to executing arbitrary remote commands.
"""Helper: Run command via SSH. Returns (output, exit_code)."""
ssh_cmd = f"ssh {user}@{host} '{command}'"
try:
output = subprocess.check_output(ssh_cmd, shell=True, timeout=timeout, text=True, stderr=subprocess.STDOUT)
return output.strip(), 0
except subprocess.CalledProcessError as e:
return e.output or "", e.returncode
save_service() uses INSERT OR REPLACE on a row identified by the unique name. In SQLite, REPLACE deletes the existing row and inserts a new one, which can change the service id and break or cascade-delete related check_results, incidents, and check_history, causing silent loss of historical monitoring data. In a healthcheck dashboard, that undermines integrity of uptime/SLA records and incident history.
The delete_service function permanently deletes a service record, and by cascade may remove associated dependent data, but the implementation provides no confirmation prompt, logging, print statement, or inline warning beyond a terse docstring. For a code file, this qualifies as a destructive operation lacking visible user disclosure.
The remove command deletes a configured service immediately via delete_service(service["id"]) and only prints a success message afterward. In this file there is no confirmation prompt, pre-action warning, or explanatory disclosure that the operation is destructive.
No suspicious patterns detected.