Back to skill

Security audit

token-panel-ultimate

Security checks across malware telemetry and agentic risk

Overview

This skill mostly matches its token-budget dashboard purpose, but its documentation includes risky root-level service installation instructions that conflict with its stated user-level design.

Review this before installing. Do not run the sudo systemd commands from BUDGET_README.md unless you have inspected and intentionally chosen the service unit; prefer a user-level service. Keep port 8765 bound to localhost, assume local processes can read spend/quota GET endpoints, use the OS keychain where possible, and avoid TOKEN_PANEL_ALLOW_PROVIDER_ENV=1 unless you mean to share those provider keys with this tool.

Vulnerability Patterns
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • 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
Findings (5)

T06 · System Persistence

Error
Location
BUDGET_README.md:133
Finding
Privileged System-Wide Service Persistence Instructions<![CDATA[ ## Vulnerability Details **File Location**: `BUDGET_README.md:133-139` **Vulnerability Type**: Privileged systemd service installation **Risk Level**: High ### Vulnerable Code ```bash ## Systemd Service # Install service sudo cp budget-collector.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable budget-collector sudo systemctl start budget-collector ``` ### Technical Analysis The installation instructions direct users to copy a service definition into the system-wide systemd directory and enable it using elevated privileges. This creates cross-session persistence and exceeds the minimum privilege required for a per-user monitoring dashboard. The instructions also conflict with the security statement in `SKILL.md`, which describes the service as a `--user` unit that runs without root privileges. Moreover, `budget-collector.service` is not present in the audited artifact. Consequently, users cannot inspect the intended unit from this package, and a same-named file from the working directory could be copied instead. Installing a service under `/etc/systemd/system/` allows its unit definition to specify arbitrary commands and potentially run them as root unless the unit explicitly drops privileges. ### Attack Path 1. An attacker places or substitutes a malicious file named `budget-collector.service` in the directory from which the documented commands are run. 2. The user follows the documentation and executes: `sudo cp budget-collector.service /etc/systemd/system/`. 3. The user reloads systemd and enables the service. 4. The attacker-controlled `ExecStart` command runs at service start and on subsequent boots. 5. If the unit does not contain an effective privilege restriction, the payload executes as root. ### Impact Assessment A successfully substituted unit could obtain persistent system-level code execution. Potential impact includes: - Arbitrary command execution as root, depending on the unit definition - Cr ...[truncated 318 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove all `sudo` and system-wide service installation instructions. 2. Include the actual service unit in the distributed artifact so it can be audited. 3. Install the unit under the current user's systemd configuration: ```bash mkdir -p ~/.config/systemd/user cp budget-collector.service ~/.config/systemd/user/ systemctl --user daemon-reload systemctl --user enable --now budget-collector ``` 4. Ensure the unit uses an absolute, trusted `ExecStart` path and includes appropriate hardening, such as: - `NoNewPrivileges=yes` - `PrivateTmp=yes` - `ProtectSystem=strict` - `ProtectHome=read-only`, with explicit writable paths - `RestrictSUIDSGID=yes` 5. Document how to disable and remove the unit. 6. Keep `SKILL.md` and `BUDGET_README.md` consistent regarding the service's privilege level. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
api.py:132
Finding
Unauthenticated Disclosure of Spending and Usage Data<![CDATA[ ## Vulnerability Details **File Location**: `api.py:132-220` **Vulnerability Type**: Missing authentication on sensitive read endpoints **Risk Level**: Medium ### Vulnerable Code ```python @app.get("/budgets") def get_budgets(): """Get current budget status for all providers.""" conn = db.get_connection() return {"budgets": db.get_budget_status(conn)} @app.get("/budgets/{provider}") def get_budget(provider: str): """Get budget for a specific provider.""" conn = db.get_connection() budget = db.get_budget(conn, provider) if not budget: raise HTTPException(status_code=404, detail=f"No budget set for {provider}") return budget @app.get("/summary/monthly") def get_monthly_summary(year: int = None, month: int = None): """Get monthly usage summary.""" now = datetime.utcnow() year = year or now.year month = month or now.month conn = db.get_connection() return { "year": year, "month": month, "providers": db.get_monthly_summary(conn, year, month), } @app.get("/summary/daily/{provider}") def get_daily_breakdown(provider: str, days: int = 30): """Get daily breakdown for a provider.""" conn = db.get_connection() return { "provider": provider, "days": db.get_daily_breakdown(conn, provider, days), } @app.get("/status") def get_status(): """Get overall budget status (for agent system prompt).""" conn = db.get_connection() budgets = db.get_budget_status(conn) alerts = [] status_parts = [] for b in budgets: provider = b["provider"] pct = b["percent"] status = b["status"] if provider == "manus": status_parts.append(f"{provider}={pct:.0f}% ({b['used']}/{b['limit']} credits)") else: status_parts.append(f"{provider}={pct:.0f}% (${b['used']:.2f}/${b['limit']:.2f})") if status == "critical": alerts.append(f"🔴 CRITICAL: {provider} at ...[truncated 1956 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require authentication for all endpoints that expose usage, spending, quota, or budget data. 2. Reuse a dependency-based authentication function for both GET and POST routes. 3. Compare tokens with `secrets.compare_digest()` to avoid ordinary string-comparison timing differences. 4. Separate read and write credentials if integrations require read-only access. 5. Continue binding explicitly to `127.0.0.1` and reject attempts to configure a public bind address unless a secure authentication mode is enabled. 6. Consider Unix-domain sockets with restrictive file permissions for local-only integrations. 7. Add tests confirming that sensitive GET endpoints return `401` or `403` when no valid read token is provided. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/chatgpt-usage-fetch.py:237
Finding
OpenAI Usage File May Be Created with Excessive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/chatgpt-usage-fetch.py:237-240` **Vulnerability Type**: Insecure local storage permissions **Risk Level**: Low ### Vulnerable Code ```python elif '--update' in args: USAGE_JSON_PATH.parent.mkdir(parents=True, exist_ok=True) with open(USAGE_JSON_PATH, 'w') as f: json.dump(output, f, indent=2) ``` ### Technical Analysis The script writes `chatgpt-usage.json` with the standard `open()` function and does not enforce owner-only permissions on either the output file or its parent directory. For a newly created file, permissions are determined by the process umask. Under a permissive umask, the file may be readable by group members or other local users. If the file already exists with broad permissions, opening it for writing preserves those permissions. This differs from the Claude and Manus output writers, which explicitly create or set their files to mode `0600`. It also contradicts the Skill's declaration that provider usage files are created owner-only. ### Attack Path 1. The user runs the script with `--update` under a permissive umask, or an attacker pre-creates the target file with broad read permissions. 2. The script writes usage information without correcting those permissions. 3. Another local user or process reads the resulting JSON file. 4. The reader obtains OpenAI model, tier, rate-limit, utilization, and update-timing information. ### Impact Assessment The issue can expose local account-usage metadata, including: - OpenAI model usage and availability - Request and token rate-limit capacity - Utilization percentages - Probe timing and activity It does not directly expose the API key because the key is not written into the output. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions Create the file with an explicit owner-only mode and enforce restrictive permissions on the parent directory: ```python USAGE_JSON_PATH.parent.mkdir(parents=True, exist_ok=True) os.chmod(USAGE_JSON_PATH.parent, 0o700) fd = os.open( str(USAGE_JSON_PATH), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600, ) with os.fdopen(fd, "w") as f: json.dump(output, f, indent=2) os.chmod(USAGE_JSON_PATH, 0o600) ``` Also reject symlink targets or use a safe atomic-write strategy if untrusted local users can modify the parent directory. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/gemini-usage-fetch.py:259
Finding
Gemini Usage File May Be Created with Excessive Permissions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/gemini-usage-fetch.py:259-262` **Vulnerability Type**: Insecure local storage permissions **Risk Level**: Low ### Vulnerable Code ```python elif '--update' in args: USAGE_JSON_PATH.parent.mkdir(parents=True, exist_ok=True) with open(USAGE_JSON_PATH, 'w') as f: json.dump(output, f, indent=2) ``` ### Technical Analysis The Gemini usage writer relies on ambient umask settings and does not enforce mode `0600` on the output file or mode `0700` on the parent directory. A newly created file may consequently be readable by other local users, while an existing broadly readable file retains its prior mode. The output contains provider quota and account-capability metadata. Although it does not contain the Gemini API key, its handling does not meet the owner-only storage behavior declared by the Skill. ### Attack Path 1. The script is run with `--update` under a permissive umask, or the target file already has broad permissions. 2. The output is written without correcting file or directory modes. 3. Another local account or process reads `gemini-usage.json`. 4. The reader obtains account tier, accessible model count, probe status, and rate-limit information. ### Impact Assessment Potentially disclosed information includes: - Detected Gemini account tier - Number of models visible to the account - Probe activity and token consumption - Published rate-limit data associated with the selected tier - Timing of account checks The API credential itself is not included in the output. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions Use explicit secure creation and permissions: ```python USAGE_JSON_PATH.parent.mkdir(parents=True, exist_ok=True) os.chmod(USAGE_JSON_PATH.parent, 0o700) fd = os.open( str(USAGE_JSON_PATH), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600, ) with os.fdopen(fd, "w") as f: json.dump(output, f, indent=2) os.chmod(USAGE_JSON_PATH, 0o600) ``` Use an atomic temporary file in the same trusted directory followed by `os.replace()` if interruption-safe updates are required. Validate that the target is not a symlink before writing. ]]>

T08 · Insecure Dependencies

Warning
Location
requirements.txt:7
Finding
Dependency Installation Is Not Reproducible or Hash-Verified<![CDATA[ ## Vulnerability Details **File Location**: `requirements.txt:7-10` **Vulnerability Type**: Unpinned and unhashed dependency resolution **Risk Level**: Medium ### Vulnerable Code ```text fastapi>=0.109.1,<1.0 uvicorn>=0.30.0,<1.0 httpx>=0.27.0,<1.0 pydantic>=2.7.0,<3.0 ``` The documented installation command is: ```bash pip install -r requirements.txt ``` ### Technical Analysis The requirements define broad version ranges rather than an audited, exact dependency graph. They also do not provide package hashes. Each installation can therefore resolve to different direct and transitive package versions depending on installation date and index state. The lower bounds avoid some known-vulnerable historical releases, but they do not protect against: - A future compromised release that still satisfies the range - Newly discovered vulnerabilities in permitted versions - Unexpected transitive dependency changes - Dependency-index or mirror compromise - Security-relevant behavioral changes within the permitted range Python package installation may execute package build logic, and installed dependencies execute with the privileges of the user running the application. ### Attack Path 1. A malicious or compromised dependency release is published within an allowed version range, or an index/mirror serves a modified package. 2. A user runs `pip install -r requirements.txt`. 3. The resolver selects the compromised version because no lockfile or hash restricts it. 4. Malicious installation or runtime code executes under the installing user's account. 5. That code may access the same local credentials, usage files, database, and network permissions available to the Skill. ### Impact Assessment A compromised dependency could obtain arbitrary code execution with the privileges of the user performing installation or running the service. Accessible assets may include: - Token Panel's credential store and environment variables - Local usage JSON files and SQLit ...[truncated 275 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Generate and distribute a lockfile containing exact direct and transitive versions. 2. Include cryptographic hashes and install with: ```bash pip install --require-hashes -r requirements.lock ``` 3. Build the lockfile from a trusted package index in a controlled environment. 4. Review and regularly update pinned versions using automated vulnerability scanning. 5. Retain `requirements.in` or equivalent broad constraints only as the human-maintained source; install production deployments from the locked file. 6. Prefer prebuilt, verified wheels and avoid unnecessary source builds. 7. Run installation and the service as an unprivileged user, never with `sudo pip`. ]]>

SkillSpector

By NVIDIA
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • 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
Findings (3)

Credential Access

High
Category
Privilege Escalation
Content
scope: "One path only: its own credential fallback file ~/.openclaw/data/token-panel/credentials, unlinked by --logout when the last provider is removed. The path is validated before deletion (absolute, inside $HOME, a regular file, not a symlink, owned by you, and beside a .token-panel-store marker this tool wrote). Nothing else is ever deleted; the SQLite database is not removed."
      env_read:
        required: true
        scope: "Namespaced TOKEN_PANEL_* variables (credentials, TOKEN_PANEL_API_TOKEN, TOKEN_PANEL_READ_TRANSCRIPTS, TOKEN_PANEL_ALLOW_PROVIDER_ENV). Generic provider variables belonging to other tools (OPENAI_API_KEY, ANTHROPIC_ADMIN_API_KEY, GEMINI_API_KEY, MANUS_API_KEY) are read ONLY with TOKEN_PANEL_ALLOW_PROVIDER_ENV=1. No .env files are scraped."
      process_exec:
        required: true
        scope: "The OS keychain helpers only: `secret-tool` (Linux) for store/lookup/clear, and `security` (macOS) for lookup/delete. Secrets are never passed as command-line arguments — macOS writes go through Security.framework, Linux writes go over stdin."
Confidence
80% confidence
Finding
The skill is documented to read environment variables containing credentials, including generic provider API-key variables when TOKEN_PANEL_ALLOW_PROVIDER_ENV=1 is set. Even though this is framed as opt-in and avoids .env scraping, reading credentials from the environment still expands the blast radius of any compromise and risks unintended reuse of secrets meant for other tools.

Credential Access

High
Category
Privilege Escalation
Content
- dashboard
    license: MIT
    notes:
      security: "Runs a REST API bound to 127.0.0.1:8765. Its GET endpoints are unauthenticated and report your spend, so do not expose the port; every POST additionally requires X-Token-Panel-Token and is closed entirely unless TOKEN_PANEL_API_TOKEN is set. SQLite database is local, 0600 in a 0700 directory, and holds counts and opaque ids only. Credentials are ones you supply, sealed in the OS keychain. The systemd unit is a --user unit: it runs as you, never as root, and carries no hardcoded username."
---

# Token Panel Ultimate
Confidence
87% confidence
Finding
The skill exposes unauthenticated GET endpoints on a local REST API that report spend and quota information. Even though it is bound to 127.0.0.1, any local process running as the same user or on the host may query these endpoints, creating an information-disclosure risk in multi-process, shared-user, or compromised-host scenarios.

Credential Access

High
Category
Privilege Escalation
Content
**What it does NOT do:** no credential reuse, no browser-token extraction, no silent secrets
scraping, no `.env` reads, no message content in the database, no telemetry, no third-party
endpoint. If no OS keychain exists, the CLI warns on every use before falling back to an
owner-only `0600` file inside a `0700` directory. Database and usage files are created `0600`
inside a `0700` directory rather than inheriting an ambient umask. The local API allows CORS only
from localhost dashboard origins, not `*`.
Confidence
78% confidence
Finding
The skill documents a fallback to an owner-only 0600 file when no OS keychain exists. While permissions are restricted, file-based secret storage is materially weaker than a keychain because it depends on local filesystem protections and increases exposure to backup leakage, accidental copying, and local compromise.

VirusTotal

VirusTotal findings are pending for this skill version.

View on VirusTotal

Static analysis

No suspicious patterns detected.