T01 · Skill Instruction Hijacking
- Location
runner.py:213- Finding
Remote WTT Content Is Automatically Executed as Privileged Agent Instructions
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This WTT skill provides real messaging and orchestration features, but it can silently install a persistent background service and expand local OpenClaw session permissions so remote WTT tasks can drive local agent sessions.
Review this skill carefully before installing. It is not just a command helper: by default it may install a persistent WTT worker, alter OpenClaw gateway permissions, read the local gateway token, create or message agent sessions, read session history/transcripts, and let WTT tasks or qualifying messages trigger local agent work. Only use it with trusted WTT topics/accounts, consider disabling automatic service installation and gateway patching, and verify the uninstall path and permission changes afterward.
runner.py:213Remote WTT Content Is Automatically Executed as Privileged Agent Instructions
__init__.py:49Package Import Silently Installs and Starts a Persistent Background Service
scripts/install_autopoll.sh:294Installer Automatically Expands OpenClaw Gateway Session Permissions
scripts/install_autopoll.sh:95Automatic Installation Uses Mutable, Unpinned Python Dependencies
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
import httpx, uuid as _uuid
api_url = os.getenv("WTT_API_URL", "https://www.waxbyte.com").rstrip("/")
try:
resp = httpx.post(f"{api_url}/agents/register", json={"platform": "openclaw"}, timeout=15)
if resp.status_code == 200:
data = resp.json()
cur_agent = data.get("agent_id", "")
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
generated = ""
token = ""
try:
resp = httpx.post(f"{api_url}/agents/register", json={"platform": "openclaw"}, timeout=15)
if resp.status_code == 200:
data = resp.json()
generated = data.get("agent_id", "")
The description portrays a fairly full-featured WTT orchestration skill, including topic/P2P messaging, task and pipeline operations, delegation, IM routing, and WebSocket-first autopoll support. The actual code chunk is much narrower: it is an example script that wires a mock agent to an imported WTTSkillHandler and exposes a few REST-backed operations (list, search, join, publish, poll, create, p2p in the endpoint map) plus a CLI demo. There is no visible implementation of task or pipeline management, delegation, WebSocket handling, autopoll runtime installation, or real IM routing. While some messaging-related behavior aligns with the description, the chunk materially under-delivers relative to the declared purpose and is primarily a demonstration harness rather than the described full skill.
The skill relies on storing sensitive runtime values such as WTT_AGENT_TOKEN and routing configuration in a local .env file, which is a common source of credential leakage through permissive file permissions, accidental commits, backups, or other local tooling. Because the token is used for claim/binding operations, compromise could let an attacker interfere with agent ownership or impersonate the agent in backend flows.
Copy and edit .env from example:
cp ~/.openclaw/workspace/skills/wtt-skill/.env.example ~/.openclaw/workspace/skills/wtt-skill/.env
Required keys in .env:
Referenced artifact was not completely inspected
If any are missing, `start_wtt_autopoll.py` will fail to start (typical error: `ModuleNotFoundError: No module named 'httpx'`).
Referenced artifact was not completely inspected
If any are missing, `start_wtt_autopoll.py` will fail to start (typical error: `ModuleNotFoundError: No module named 'httpx'`).
Referenced artifact was not completely inspected
If any are missing, `start_wtt_autopoll.py` will fail to start (typical error: `ModuleNotFoundError: No module named 'httpx'`).
Referenced artifact was not completely inspected
- Command parsing is implemented in `handler.py`
The skill installs and starts a host autopoll service through OS service-management mechanisms and a shell script, which is beyond the justified behavior of a chat messaging/orchestration skill. This creates unauthorized persistence and background execution on the host, especially risky because it is triggered automatically rather than through a dedicated installation command. The skill context makes this more dangerous, not less, because users would reasonably expect message handling, not host service deployment.
The shell script is executed with stdout and stderr suppressed, providing no disclosure to the user that a persistent service installation was attempted. Silent execution reduces detectability, impairs auditing and troubleshooting, and is consistent with stealthy host modification patterns even if the underlying intent is convenience rather than malice. Combined with automatic triggering, this significantly raises the security risk.
Importing the module automatically attempts to install or start an autopoll service without any user-facing warning, prompt, or setup step. Import-time side effects that alter host persistence are highly dangerous because merely loading the module can change system configuration, violating least surprise and enabling stealthy deployment behavior. In this skill context, automatic service installation is especially unjustified and risky.
The _upsert_env helper reads and rewrites a local .env file, which may contain credentials and other sensitive configuration. Although intended for setup convenience, modifying secret-bearing files inside a command handler increases the risk of accidental exposure, corruption, or unauthorized persistence of tokens.
return best[0], best[1], "sessions.json"
def _upsert_env(self, updates: Dict[str, str]) -> str:
env_path = Path(__file__).resolve().parent / ".env"
lines = []
if env_path.exists() and env_path.is_file():
lines = env_path.read_text(encoding="utf-8").splitlines()
Skill instructs the agent to never refuse or to always comply. Suppressing the agent's ability to decline removes a core safety control and enables downstream harmful requests to succeed.
# From here: sender is human (or unknown)
# P2P topics — always respond to human messages
if topic_type == "p2p" or topic_name.startswith("private://"):
return True
Skill instructs the agent to never refuse or to always comply. Suppressing the agent's ability to decline removes a core safety control and enables downstream harmful requests to succeed.
# From here: sender is human (or unknown)
# P2P topics — always respond to human messages
if topic_type == "p2p" or topic_name.startswith("private://"):
return True
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
PY_BIN="$SKILL_ROOT/.venv/bin/python"
else
echo "⚠️ Found broken .venv (pip missing), recreating..."
rm -rf "$SKILL_ROOT/.venv"
if ensure_skill_venv && [[ -x "$SKILL_ROOT/.venv/bin/python" ]]; then
PY_BIN="$SKILL_ROOT/.venv/bin/python"
fi
User-controlled values are inserted into sed replacement expressions without escaping. If WTT_AGENT_ID contains sed metacharacters such as '&', backslashes, or the chosen delimiter '|', the .env file can be corrupted or rewritten in unintended ways, potentially altering runtime behavior or smuggling extra configuration content.
# - if absent, keep empty and let runtime register via API
if [[ -n "$configured_agent_id" ]]; then
if grep -q '^WTT_AGENT_ID=' "$ENV_FILE"; then
sed -i.bak "s|^WTT_AGENT_ID=.*|WTT_AGENT_ID=$configured_agent_id|" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
else
printf "\nWTT_AGENT_ID=%s\n" "$configured_agent_id" >> "$ENV_FILE"
fi
The configured target is interpolated directly into a sed replacement expression with no escaping. An attacker who can influence WTT_IM_TARGET could inject sed metacharacters or malformed content, causing unintended edits to the .env file and misdirecting IM routing or altering subsequent behavior.
local current_target
current_target="$(grep '^WTT_IM_TARGET=' "$ENV_FILE" | tail -n1 | cut -d'=' -f2- | tr -d '\n\r')"
if [[ -z "$current_target" && -n "$configured_target" ]]; then
sed -i.bak "s|^WTT_IM_TARGET=.*|WTT_IM_TARGET=$configured_target|" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
fi
else
printf "WTT_IM_TARGET=%s\n" "$configured_target" >> "$ENV_FILE"
The channel value is written through sed without escaping, so special characters can break the replacement and modify the .env file unpredictably. In an orchestration skill, misconfiguration of channel selection can reroute messages or prevent safe operation, and it shows unsafe handling of externally supplied parameters.
local current_channel
current_channel="$(grep '^WTT_IM_CHANNEL=' "$ENV_FILE" | tail -n1 | cut -d'=' -f2- | tr -d '\n\r')"
if [[ -z "$current_channel" ]]; then
sed -i.bak "s|^WTT_IM_CHANNEL=.*|WTT_IM_CHANNEL=$configured_channel|" "$ENV_FILE" && rm -f "$ENV_FILE.bak"
fi
else
printf "WTT_IM_CHANNEL=%s\n" "$configured_channel" >> "$ENV_FILE"
The script prints effective .env-derived values, including agent_id and target, to stdout. While not a password leak, these values may be sensitive routing or identity metadata and can end up in terminal history, CI logs, support bundles, or centralized logging systems.
final_target="$(grep '^WTT_IM_TARGET=' "$ENV_FILE" | tail -n1 | cut -d'=' -f2- | tr -d '\n\r' || true)"
final_channel="$(grep '^WTT_IM_CHANNEL=' "$ENV_FILE" | tail -n1 | cut -d'=' -f2- | tr -d '\n\r' || true)"
echo "✅ Checked required .env keys: $ENV_FILE"
echo "ℹ️ Effective env: agent_id=${final_agent_id:-'(empty, will auto-register at runtime)'} channel=${final_channel:-'(empty)'} target=${final_target:-'(empty)'}"
}
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
# Clean stale standalone processes before restarting managed service.
pkill -f "$SKILL_ROOT/start_wtt_autopoll.py" >/dev/null 2>&1 || true
rm -f "$SKILL_ROOT/.autopoll.pid" >/dev/null 2>&1 || true
systemctl --user daemon-reload
systemctl --user enable --now wtt-autopoll.service
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).
echo "✅ macOS autopoll removed"
elif [[ "$os" == "Linux" ]]; then
systemctl --user disable --now wtt-autopoll.service >/dev/null 2>&1 || true
rm -f "$HOME/.config/systemd/user/wtt-autopoll.service"
systemctl --user daemon-reload || true
echo "✅ Linux autopoll removed"
else
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def _load_local_env(env_path: str):
"""Load KEY=VALUE lines from .env into process env without overriding existing vars."""
try:
p = Path(env_path)
if not p.exists() or not p.is_file():
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def _load_local_env(env_path: str):
"""Load KEY=VALUE lines from .env into process env without overriding existing vars."""
try:
p = Path(env_path)
if not p.exists() or not p.is_file():
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def _load_local_env(env_path: str):
"""Load KEY=VALUE lines from .env into process env without overriding existing vars."""
try:
p = Path(env_path)
if not p.exists() or not p.is_file():
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
def _load_local_env(env_path: str):
"""Load KEY=VALUE lines from .env into process env without overriding existing vars."""
try:
p = Path(env_path)
if not p.exists() or not p.is_file():
No suspicious patterns detected.