Back to skill

Security audit

LLM Cost Tracker

Security checks for vulnerabilities and agentic risk

Overview

This cost tracker mostly matches its purpose, but review is warranted because a dry-run pruning command actually deletes data and normal reports automatically handle API credentials.

Install only if you are comfortable with the skill reading OpenClaw session history, creating a local usage database, and using an OpenRouter API key for account-summary data. Do not rely on scripts/prune_usage.py --dry-run until fixed, because it deletes rows. Prefer an explicit, least-privileged API key and avoid running setup or the tracker as root.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/run_tracker.py:85
Finding

Automatic API Credential Discovery Exposes the Secret in Process Arguments

Content
View full analysis
OpenClaw auth-profiles.json """ key = os.environ.get("OPENROUTER_API_KEY") if key: return key auth_candidates = [ os.path.expanduser("~/.openclaw/agents/main/agent/auth-profiles.json"), "/data/.openclaw/agents/main/agent/auth-profiles.json", "/root/.openclaw/agents/main/agent/auth-profiles.json", ] for auth_path in auth_candidates: if os.path.exists(auth_path): try: with open(auth_path) as f: data = json.load(f) key = data.get("profiles", {}).get("openrouter:default", {}).get("key") if key: return key except Exception: continue return None def get_openrouter_key_info(api_key): """ Fetch key info from /api/v1/auth/key. Returns dict with: usage, usage_daily, usage_weekly, usage_monthly, limit, limit_remaining. """ if not api_key: return None try: cmd = ["curl", "-s", "-H", f"Authorization: Bearer {api_key}", "https://openrouter.ai/api/v1/auth/key"] result = subprocess.run(cmd, capture_output=True, text=True, timeout=20) if result.returncode == 0: return json.loads(result.stdout).get("data", {}) except Exception: pass return None ``` The credential lookup and network request are invoked during ordinary report generation: ```python api_key = get_openrouter_api_key() ... key_info = get_openrouter_key_info(api_key) ``` ### Technical Analysis Every normal execution of `run_tracker.py` automatically searches the env ...[truncated 2219 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Note
Location
scripts/setup.py:35
Finding

Unbounded Dependency Installation Creates Supply-Chain Risk

Content
View full analysis
=0.9.0 ``` `scripts/setup.py`: ```python # Install deps if REQUIREMENTS.is_file(): result = subprocess.run([sys.executable, "-m", "pip", "install", "-r", str(REQUIREMENTS)], capture_output=True, text=True) ``` ### Technical Analysis The dependency declaration accepts any `tabulate` release equal to or newer than version 0.9.0. No upper bound, exact version, lock file, or package-integrity hash is supplied. The setup script passes this requirement directly to `pip`, allowing dependency resolution to change over time without a corresponding review of the Skill. There is no evidence that `tabulate` is a typosquatted package or that the configured package source is currently malicious. The risk arises because future installations automatically trust future releases and the active Python package index configuration. If an accepted release or package source is compromised, package installation or subsequent import could execute unreviewed code. ### Attack Path 1. A future accepted dependency release or configured package source is compromised, or an unsafe release is published. 2. A user runs `python3 scripts/setup.py` or follows the documented `pip install` command. 3. `pip` resolves the unconstrained requirement to the affected release. 4. Installation-time behavior or later package import executes the introduced code with the privileges of the user running setup or the tracker. 5. The malicious package gains access to files, environment variables, and network capabilities available to that user. ### Impact Assessment Impact is bounded by the privileges of the account running `pip` or executing the tracker. In a normal user installation, a compromised dependency could a ...[truncated 339 chars]
Remediation
View remediation
``` 2. Generate and enforce cryptographic hashes, such as with a hash-locked requirements file and: ```bash pip install --require-hashes -r requirements.txt ``` 3. Maintain a lock file through a reproducible dependency-management tool and review dependency updates before changing the lock. 4. Install dependencies inside an isolated virtual environment instead of the system Python environment. 5. Avoid running setup as root unless strictly necessary. 6. Configure an explicitly trusted package index and prevent unreviewed environment-level `pip` configuration from silently redirecting package resolution. 7. Add automated dependency vulnerability and integrity checks to the release process. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (27)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

If the skill also installs dependencies with pip, performs setup validation, or checks the filesystem beyond cost reporting, that is a meaningful behavior mismatch from a user-facing reporting tool. Unexpected installation or setup actions can modify the host environment and expand attack surface, particularly when triggered through an automation framework.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

If the skill also installs dependencies with pip, performs setup validation, or checks the filesystem beyond cost reporting, that is a meaningful behavior mismatch from a user-facing reporting tool. Unexpected installation or setup actions can modify the host environment and expand attack surface, particularly when triggered through an automation framework.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The script performs deletion unconditionally by calling delete_before(conn, cutoff_iso) before checking args.dry_run, so '--dry-run' still permanently removes records. This is dangerous because operators may trust dry-run mode for impact assessment and accidentally destroy historical usage/accounting data, undermining auditability and cost reporting.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The printed '[DRY RUN]' message is misleading because the rows have already been deleted earlier in execution. In a cost-tracking skill, losing request_facts records directly corrupts historical reporting, reconciliation, and forensic analysis, making this more dangerous than a generic logging bug.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The markdown describes prune_usage.py as deleting old rows and shows a --vacuum example that performs deletion, but it does not explicitly warn that this is destructive and may permanently remove historical usage data. For markdown files, user-facing documentation should disclose behaviors that can affect user data or system integrity.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill declares executable behaviors involving environment access, file reads, network access, and shell execution, but does not declare any explicit tool scope or permission boundaries. This increases the risk of over-privileged execution because an agent may run the skill with broader capabilities than users expect, especially since the skill reads credentials and executes scripts.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The phrase "collect usage data" is ambiguous and especially risky because the documentation says it silently populates a database and should not send chat output. Ambiguous silent triggers are dangerous in automation contexts because they can initiate hidden state changes without clear operator intent or visibility.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The phrase "collect usage data" is ambiguous and especially risky because the documentation says it silently populates a database and should not send chat output. Ambiguous silent triggers are dangerous in automation contexts because they can initiate hidden state changes without clear operator intent or visibility.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill explicitly describes silent collection that populates a database but does not prominently warn that local state will be modified. Hidden or quiet state mutation is dangerous because users may believe they are requesting a read-only report when the skill is actually writing or updating persisted data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

This code stores raw usage JSON and raw response metadata in an append-only database, and the script later writes those records to disk. While the module docstring says it collects request facts, it does not clearly warn users that request-derived metadata will be persistently stored locally, which is a data-handling behavior relevant to privacy and system state.

Content

No source excerpt is available for this finding.

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/prune_usage.py (reported line 17)May include surrounding context.

python
import sqlite3
from datetime import datetime, timezone, timedelta

SKILL_DIR = __import__("os").path.dirname(__import__("os").path.dirname(__import__("os").path.abspath(__file__)))
DB_PATH = __import__("os").path.join(SKILL_DIR, "config", "usage.db")

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Confidence
75% confidence
Finding

Dynamic import() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

Content

Scanner excerpt · scripts/prune_usage.py (reported line 18)May include surrounding context.

python
from datetime import datetime, timezone, timedelta

SKILL_DIR = __import__("os").path.dirname(__import__("os").path.dirname(__import__("os").path.abspath(__file__)))
DB_PATH = __import__("os").path.join(SKILL_DIR, "config", "usage.db")


def delete_before(conn, cutoff_iso):

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest describes this skill as tracking OpenRouter token usage/cost, generating reports, and silently collecting usage data. This file implements deletion of historical rows from the usage database, which is a data-retention/destructive maintenance operation not mentioned in the manifest's stated behavior.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

For a skill framed around cost tracking, reporting windows, and usage collection, a CLI capability to permanently delete rows and vacuum the database is an additional administrative function. That maintenance capability is not an obvious requirement of generating reports or collecting usage data as described in the manifest.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

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.

Content

Scanner excerpt · scripts/collect_usage.py (reported line 26)May include surrounding context.

python
# ─── Schema (kept in sync with collect_usage.py) ─────────────────────────
SCHEMA = """
CREATE TABLE IF NOT EXISTS request_facts (
    id                      INTEGER PRIMARY KEY AUTOINCREMENT,
    openrouter_request_id   TEXT UNIQUE NOT NULL,
    created_at_utc          TEXT NOT NULL,

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

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.

Content

Scanner excerpt · scripts/run_tracker.py (reported line 21)May include surrounding context.

python
# ─── Schema (kept in sync with collect_usage.py) ─────────────────────────
SCHEMA = """
CREATE TABLE IF NOT EXISTS request_facts (
    id                      INTEGER PRIMARY KEY AUTOINCREMENT,
    openrouter_request_id   TEXT UNIQUE NOT NULL,
    created_at_utc          TEXT NOT NULL,

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest describes this entrypoint as producing LLM cost/token reports, which implies read-oriented analytics over previously collected data. However, get_conn() executes CREATE TABLE and CREATE INDEX statements and enables WAL mode, causing the reporting path to modify the SQLite database on startup rather than only reading from it.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The script automatically reads OpenRouter API credentials from multiple filesystem locations, including user and root OpenClaw auth profiles, without explicit user consent at runtime. In an agent-skill context, this broad secret discovery increases the blast radius: a seemingly harmless reporting action can silently access credentials outside the skill's own configuration boundary and use them over the network.

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/run_tracker.py (reported line 124)May include surrounding context.

python
try:
        cmd = ["curl", "-s", "-H", f"Authorization: Bearer {api_key}",
               "https://openrouter.ai/api/v1/auth/key"]
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=20)
        if result.returncode == 0:
            return json.loads(result.stdout).get("data", {})
    except Exception:

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The debug mode prints raw request identifiers and stored raw usage payloads, which may contain sensitive metadata and could expose account activity, model usage details, or identifiers useful for correlation. In an agent environment, debug output may be forwarded to chat, logs, or external observers, making this disclosure more dangerous than a local developer-only script.

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/setup.py (reported line 37)May include surrounding context.

python
# Install deps
    if REQUIREMENTS.is_file():
        result = subprocess.run([sys.executable, "-m", "pip", "install", "-r", str(REQUIREMENTS)],
                             capture_output=True, text=True)
        if result.returncode == 0:
            print("[OK] Dependencies installed")

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The configuration and cron examples hard-code Asia/Hong_Kong / HKT +8 as the default timezone, which is a locale-specific assumption presented as the normal default behavior. The file does not offer an explicit user choice or explain why this locale constraint is required for a region-specific use case.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

Auto-detecting an API key from a local auth profile or environment variable without an explicit credential-access warning can surprise users and causes the skill to touch sensitive secrets as part of normal execution. While credential discovery is common for integrations, it should be transparent because it expands the sensitivity of the operation and the consequences of mis-triggering.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The config hard-codes the timezone to "Asia/Hong_Kong" with a matching UTC offset, which imposes a specific locale setting. Under the policy, locale constraints should either offer user choice or be clearly documented and justified as region-specific.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
91% confidence
Finding

The dependency is specified with a lower-bound only constraint (tabulate>=0.9.0), which allows installation of any future version, including versions that may introduce breaking changes or vulnerable transitive behavior. While tabulate is a common low-risk library, unpinned dependencies reduce build reproducibility and can expose the skill to supply-chain risk if a compromised or insecure release is later published.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
tabulate>=0.9.0