Back to skill

Security audit

TBOT Controller

Security checks for vulnerabilities and agentic risk

Overview

This trading-control skill has useful TBOT functions, but its webhook mode can send authenticated trade signals without the documented confirmation guard and can expose webhook secrets.

Review before installing. Use this only with a clearly identified paper/live TBOT runtime, do not let agents invoke JSON mode for previews, avoid TBOT_WEBHOOK_URL values outside trusted local endpoints, and assume JSON mode may send real trade instructions and reveal the webhook key unless the skill is fixed.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/tbotjson.py:319
Finding

Webhook secret is printed and transmitted to a caller-controlled destination

Content
View full analysis
Dict[str, str]: """Load the first .env we can find from candidate runtime dirs.""" for d in _candidate_runtime_dirs(): env_path = d / ".env" if env_path.exists() and env_path.is_file(): return _parse_dotenv(env_path) return {} def _resolve_webhook_key(cli_key: str) -> str: if cli_key: return cli_key env_key = os.getenv("WEBHOOK_KEY", "").strip() if env_key: return env_key dotenv = _discover_runtime_env() for k in ("WEBHOOK_KEY", "TBOT_WEBHOOK_KEY", "TVWB_KEY", "TV_WEBHOOK_KEY"): v = (dotenv.get(k) or "").strip() if v: return v unique_key = _resolve_unique_key(dotenv) if unique_key: return _generate_webhook_key(unique_key) for k, v in dotenv.items(): if re.search(r"webhook", k, re.I) and re.search(r"key", k, re.I) and v.strip(): return v.strip() return "" def _resolve_unique_key(dotenv: Dict[str, str]) -> str: unique_key = os.getenv("TVWB_UNIQUE_KEY", "").strip() if unique_key: return unique_key unique_key = (dotenv.get("TVWB_UNIQUE_KEY") or "").strip() if unique_key: return unique_key for d in _candidate_runtime_dirs(): key_path = d / ".keyfile" if key_path.exists() and key_path.is_file(): try: return key_path.read_text().strip() except Exception: continue return "" def post_json(url: str, payload: Dict[str, Any]) -> Tuple[int, str]: data = json.dumps(payload).encode("utf-8") req = request.Request( url, data=data, headers={"Content-Type": "application/json"}, method="POST", ...[truncated 2965 chars]
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/tbotjson.py:303
Finding

Trade-capable webhook transmission bypasses the documented confirmation requirement

Content
View full analysis
int: p = argparse.ArgumentParser( description="Generate schema-valid TBOT webhook JSON and send to TBOT webhook" ) p.add_argument("--ticker", required=True) p.add_argument("--direction", default="") p.add_argument("--orderRef", default="") p.add_argument("--contract", default="") p.add_argument( "--close", type=int, default=None, help="Close position qty (sets strategy.close, contract stock, qty metric)", ) p.add_argument("--currency", default=os.getenv("DEFAULT_CURRENCY", "USD")) p.add_argument("--timeframe", default=os.getenv("DEFAULT_TIMEFRAME", "1D")) p.add_argument("--clientId", type=int, default=int(os.getenv("DEFAULT_CLIENT_ID", "1"))) p.add_argument("--metric", action="append", default=[]) p.add_argument("--key", default=os.getenv("WEBHOOK_KEY", "")) p.add_argument("-o", "--out", default="") p.add_argument("--url", default=os.getenv("TBOT_WEBHOOK_URL", ""), help="TBOT webhook URL (or set TBOT_WEBHOOK_URL)") args = p.parse_args() args.key = _resolve_webhook_key(args.key) if not args.key: raise SystemExit("Missing webhook key (set WEBHOOK_KEY or put it in the runtime .env)") schema = load_schema() payload = build_payload(args) # If user did not provide direction and did not use --close, assume close intent. if not args.direction: args.direction = "strategy.close" payload["direction"] = args.direction validate_schema(payload, schema) out = json.dumps(payload, indent=2) if args.out: Path(args.out).write_text(out + "\n") print(out) args.url = _resolve_webhook_url(args.url) if not args.url: raise Sy ...[truncated 2486 chars]
Remediation
View remediation

T01 · Skill Instruction Hijacking

Error
Location
README.md:81
Finding

Documentation falsely describes an unconditional network sender as an offline JSON generator

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
scripts/requirements.txt:1
Finding

Automatically installed Python dependency is not reproducibly pinned

Content
View full analysis
=4.0.0 ``` ```bash REQ_FILE="$SCRIPT_DIR/requirements.txt" # Prefer uv (OpenClaw-native). It creates an isolated env and installs deps on demand. if command -v uv >/dev/null 2>&1; then PYTHON_EXEC=(uv run --no-project --with-requirements "$REQ_FILE" python3) ``` ### Technical Analysis The requirement specifies only a lower version bound. The entrypoint uses `uv` to resolve and install dependencies on demand, so different invocations may execute different future releases of `jsonschema` and its transitive dependencies. No lockfile, exact version constraint, or package hash is present. Consequently, reviewed source code does not fully determine the code that will execute at runtime. This does not prove that the current dependency is malicious. It is a supply-chain hardening weakness that increases exposure to compromised releases, incompatible updates, malicious package-index configuration, or future transitive dependency changes. ### Attack Path 1. The user invokes `scripts/tbot.sh`. 2. `uv` resolves `jsonschema>=4.0.0` and its transitive dependencies against the configured package index. 3. A compromised package release, compromised index, or unsafe alternate index supplies malicious dependency code. 4. The dependency is installed into the isolated environment. 5. Python imports the package during `tbotjson.py` execution. 6. Malicious package initialization code executes with the same local privileges as the skill. ### Impact Assessment A compromised dependency could access files, environment variables, runtime credentials, webhook keys, database paths, and network resources available to the invoking user. It could also modify payloads or execute arbitrary commands with that user's privileges. The ...[truncated 130 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
Findings (29)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill presents itself as a controlled automation interface with DB-first queries, yet it also documents direct webhook transmission, secret loading from runtime configuration, and immediate action dispatch. In a trading context, that mismatch is especially dangerous because it can convert a seemingly informational skill into one capable of initiating trade-related actions over the network.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The skill presents itself as a controlled automation interface with DB-first queries, yet it also documents direct webhook transmission, secret loading from runtime configuration, and immediate action dispatch. In a trading context, that mismatch is especially dangerous because it can convert a seemingly informational skill into one capable of initiating trade-related actions over the network.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The JSON mode section contains direct internal contradictions about whether network activity occurs. Security controls, reviewers, and users rely on this documentation to understand execution risk; contradictory guidance can lead to unsafe automation of trade-triggering requests under the false assumption that the mode is offline-only.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The documentation simultaneously claims JSON mode sends payloads to a TBOT webhook and that it performs no network calls or broker actions. This contradiction can cause an agent or user to treat a state-changing network action as harmless output generation, which is highly risky when the endpoint may trigger live or paper trades.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/tbotctl.py (reported line 43)May include surrounding context.

python
"Set it to the folder that contains docker-compose.yml (e.g. .../openclaw-on-tradingboat)."
        )

    # UX guardrails: help first-time users when .env is missing.
    compose_yml = os.path.join(compose_dir, "docker-compose.yml")
    if not os.path.exists(compose_yml):
        raise SystemExit(

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/tbotctl.py (reported line 62)May include surrounding context.

python
"Set it to the folder that contains docker-compose.yml (e.g. .../openclaw-on-tradingboat)."
        )

    # UX guardrails: help first-time users when .env is missing.
    compose_yml = os.path.join(compose_dir, "docker-compose.yml")
    if not os.path.exists(compose_yml):
        raise SystemExit(

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/tbotctl.py (reported line 51)May include surrounding context.

python
"Set COMPOSE_DIR to the openclaw-on-tradingboat folder."
        )

    env_path = os.path.join(compose_dir, ".env")
    if not os.path.exists(env_path):
        sample = os.path.join(compose_dir, ".env.sample")
        stable_dotenv = os.path.join(compose_dir, "stable", "tbot", "dotenv")

Credential Access

High
Category
Privilege Escalation
Confidence
92% confidence
Finding

The documented behavior states that the webhook key will be pulled from environment variables or an auto-discovered runtime .env file. In an agent skill, automatic retrieval of secrets from local configuration increases the chance that sensitive credentials are used without explicit operator intent, enabling unauthorized authenticated requests to the TBOT webhook.

Content

Scanner excerpt · scripts/tbotjson.py (reported line 13)May include surrounding context.

python
Behavior:
 - This command ALWAYS sends the generated, schema-validated JSON payload to TBOT.
 - Webhook endpoint is taken from (in order): --url, TBOT_WEBHOOK_URL, or defaults to http://127.0.0.1:5001/webhook.
 - Webhook key is taken from (in order): --key, WEBHOOK_KEY env var, or the runtime .env file (auto-discovered).
 - orderRef defaults to Close_<TICKER>_<QTY>_<epoch_ms> when omitted.
 - If --close <QTY> is provided, direction defaults to strategy.close and contract defaults to stock.
"""

Credential Access

High
Category
Privilege Escalation
Confidence
90% confidence
Finding

The _parse_dotenv helper reads arbitrary KEY=VALUE entries from a local .env file, enabling the script to ingest secrets from files outside a narrowly defined trust boundary. In a multi-project workspace or agent environment, this broad file-based secret access can unintentionally harvest credentials from unrelated contexts.

Content

Scanner excerpt · scripts/tbotjson.py (reported line 46)May include surrounding context.

python
def _parse_dotenv(path: Path) -> Dict[str, str]:
    """Minimal .env parser (KEY=VALUE, ignores comments/blank lines)."""
    out: Dict[str, str] = {}
    try:
        for raw in path.read_text().splitlines():

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

The runtime environment discovery routine walks candidate directories and loads the first .env file it finds, creating a broad and implicit secret search capability. This can lead to credential misuse, confused-deputy behavior, or accidental use of secrets from an unintended runtime when the skill is executed from different working directories.

Content

Scanner excerpt · scripts/tbotjson.py (reported line 99)May include surrounding context.

python
def _discover_runtime_env() -> Dict[str, str]:
    """Load the first .env we can find from candidate runtime dirs."""
    for d in _candidate_runtime_dirs():
        env_path = d / ".env"
        if env_path.exists() and env_path.is_file():

Credential Access

High
Category
Privilege Escalation
Confidence
95% confidence
Finding

The specific check for '.env' existence inside discovered candidate directories is part of an automated secret-hunting flow that operationalizes credential collection. Because those credentials are later used to authenticate outbound webhook requests, the behavior materially increases the danger of unauthorized command execution against TBOT.

Content

Scanner excerpt · scripts/tbotjson.py (reported line 101)May include surrounding context.

python
def _discover_runtime_env() -> Dict[str, str]:
    """Load the first .env we can find from candidate runtime dirs."""
    for d in _candidate_runtime_dirs():
        env_path = d / ".env"
        if env_path.exists() and env_path.is_file():
            return _parse_dotenv(env_path)
    return {}

Credential Access

High
Category
Privilege Escalation
Confidence
96% confidence
Finding

The code not only reads explicit webhook keys but also recovers a unique seed from .env or .keyfile and derives a valid webhook key from it. This effectively converts locally stored secret material into active authentication for control-plane requests, which is particularly risky in an agent skill that can be invoked indirectly.

Content

Scanner excerpt · scripts/tbotjson.py (reported line 150)May include surrounding context.

python
if unique_key:
        return unique_key

    # From runtime .env
    unique_key = (dotenv.get("TVWB_UNIQUE_KEY") or "").strip()
    if unique_key:
        return unique_key

YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]

High
Category
YARA Match
Confidence
80% confidence
Finding

YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Content

Scanner excerpt · scripts/tbotjson.py (reported line 296)May include surrounding context.

python
estamp": int(time.time() * 1000),
        "ticker": args.ticker,
        "timeframe": args.timeframe,
        "currency": args.currency,
        "clientId": args.clientId,
        "key": args.key,
        "orderRef": args.orderRef,
        "contract": args.contract,
        "direction": args.direction,
        "metrics": metrics,
    }


def main() -> int:
    p = argparse.ArgumentParser(
        description="Generate schema-valid TBOT webhook JSON and send to TBOT webhook"
    )

    # Required by schema
    p.add_argument("--ticker", required=True)
    p.add_argument("--direction", default="")
    p.add_argument("--orderRef", default="")
    p.add_argument("--contract", default="")
    p.add_argument(
        "--close",
        type=int,
        default=None,
        help="Close position qty (sets strategy.close, contract stock, qty metric)",
    )

    # Defaults allowed by policy
    p.add_argument("--currency", default=os.getenv("DEFAULT_CURRENCY", "USD"))
    p.add_argument("--ti

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
97% confidence
Finding

The skill exposes powerful capabilities including shell execution, file access, environment access, and network behavior, but it declares no explicit tool scope or permission boundaries. In a trading-control skill, this omission increases the chance that an agent may invoke sensitive operations without clear sandboxing or policy enforcement, especially when the same skill can inspect runtime state, read config, and trigger webhook actions.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The JSON mode documentation does not clearly warn that sending a webhook may trigger live trading actions. Given the domain is an automated trading stack, omission of a strong execution-risk warning materially increases the chance of accidental order placement or interference with a live environment.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
96% confidence
Finding

The instruction to avoid asking the user for details while generating and sending a webhook encourages autonomous execution of potentially trade-triggering actions. In this context, inferring webhook destination, authentication material, and order metadata without a confirmation step increases the risk of unintended or unauthorized trading operations.

Content

Scanner excerpt · SKILL.md (reported line 190)May include surrounding context.

md
`json` mode generates a schema-valid TradingView-style payload and **sends it** to TBOT via webhook.

Defaults / inference rules (do not ask the user):
- **Webhook URL**: default `http://127.0.0.1:5001/webhook` (override with `TBOT_WEBHOOK_URL`).
- **Webhook key**: read from runtime `.env` (override with `WEBHOOK_KEY`).
- **orderRef**: if not provided, auto-generate `Close_<TICKER>_<QTY>_<epoch_ms>`.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
97% confidence
Finding

Automatically inferring close intent and instructing the agent not to prompt the user is unsafe in a trading skill because it collapses ambiguous natural language into an actionable trade signal. Combined with automatic webhook sending, this can produce irreversible actions against paper or live systems without sufficient human verification.

Content

Scanner excerpt · SKILL.md (reported line 194)May include surrounding context.

  • Webhook URL: default http://127.0.0.1:5001/webhook (override with TBOT_WEBHOOK_URL).
  • Webhook key: read from runtime .env (override with WEBHOOK_KEY).
  • orderRef: if not provided, auto-generate Close_<TICKER>_<QTY>_<epoch_ms>.
  • Close intent: inferred automatically; do not prompt the user.
bash
# Example (user: “close 50 NFLX now”)

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The manifest describes a controlled automation interface centered on DB-first queries and lifecycle control on explicit request. This router exposes a separate json mode and documents webhook-driven usage via TBOT_WEBHOOK_URL, which indicates event/HTTP-oriented automation not reflected in the manifest description.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/tbotctl.py (reported line 31)May include surrounding context.

python
def run(cmd: List[str], *, cwd: str | None = None) -> int:
    p = subprocess.run(cmd, cwd=cwd)
    return p.returncode

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script is explicitly designed to always transmit a generated payload to a webhook endpoint, which exceeds a narrowly described 'controlled automation interface' and removes any dry-run or approval boundary. In an agent skill context, automatic outbound actions against a trading control plane can trigger unintended or unauthorized lifecycle operations if the tool is invoked with attacker-influenced inputs.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The code searches multiple workspace directories for .env and .keyfile secrets, then derives usable webhook credentials from discovered material. That broad secret discovery behavior creates credential exposure and privilege expansion risk, especially for an agent-accessible tool that can operate across working directories without the user explicitly providing credentials.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · scripts/tbotstatus.py (reported line 6)May include surrounding context.

python
Purpose:
- Provide a *single* read-only probe endpoint that can infer where TBOT is running.
- Return structured JSON so OpenClaw can proceed without asking the user when possible.

This file MUST remain read-only:
- no start/stop/restart

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/tbotstatus.py (reported line 29)May include surrounding context.

python
def _run_capture(cmd: List[str], cwd: str | None = None) -> Tuple[int, str, str]:
    p = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True)
    return p.returncode, p.stdout, p.stderr

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The DB commands expose potentially sensitive trading data including orders, positions, PnL, symbols, timestamps, and error details, and they do so without any access control, masking, confirmation prompt, or user-facing disclosure. In an agent-skill context, this increases the risk of unintended data exfiltration to the calling agent, logs, or downstream systems even though the database is opened read-only.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
77% confidence
Finding

The markdown documents an HTTP-based health check (status health --base-url http://127.0.0.1:5001) but does not explicitly warn that it performs network requests against a service endpoint. For markdown files, operations that affect privacy or system integrity should be disclosed so users understand external communication is occurring.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.