Back to skill

Security audit

TinkerClaw Token Panel

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a transparent local AI usage dashboard that handles sensitive provider keys and spend data, with the risky behaviors disclosed, scoped, and user-controlled.

Before installing, be comfortable giving this tool provider usage credentials and running a localhost API that exposes spend data through unauthenticated GET endpoints. Keep the port private, set TOKEN_PANEL_API_TOKEN before enabling writes, prefer the OS keychain over the plaintext fallback, and enable transcript parsing, generic provider env vars, live probes, or the systemd service only when you intend those behaviors.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (86)

Tainted flow: 'value' from sys.stdin.read (line 485, user input) → subprocess.run (code execution)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

External input (network, user) flows to a code execution sink. This enables remote code execution or command injection.

Content

Scanner excerpt · secretstore.py (reported line 333)May include surrounding context.

python
kind = backend()
    if kind == "libsecret":
        r = subprocess.run(
            [_tool("secret-tool"), "store", f"--label={SERVICE} {provider}",
             "service", SERVICE, "account", provider],
            input=value, text=True,

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The code generally aligns with a local token/budget tracking API, so the primary purpose is similar. However, the description makes stronger privacy and credential-handling claims than this code supports. The implementation records more than 'only counts'—it accepts and persists provider, model, optional session_key, and Manus task_id/status metadata. It also exposes several unauthenticated GET endpoints that reveal spend/quota data to any local caller that can access the port, which is more open than the description's privacy-focused framing suggests. Finally, the description prominently claims provider credentials are sealed in the OS keychain and not passed on a command line, but this code instead relies on TOKEN_PANEL_API_TOKEN from the environment for mutation authorization and contains no evidence of OS keychain usage. The transcript parsing/probing/API-key-variable-reading claims are not present in this chunk, so they are neither confirmed nor contradicted here. Overall, there is a meaningful description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
88% confidence
Finding

The code generally aligns with the declared token-usage/budget-tracking purpose and it supports the claim that transcript parsing is opt-in and that only counts/costs are stored rather than prompt text. However, the description promises several concrete capabilities not represented in this code chunk: a local REST API, credential sealing in the OS keychain, and broader multi-provider tracking/probing. In the supplied code, Anthropic fetching is implemented, transcript scanning is gated by an opt-in environment variable, and Gemini is only used to calculate transcript-derived cost, not to fetch live provider usage. Manus is instantiated but not used. Because key advertised capabilities are absent from the supplied chunk, the description does not accurately represent what this code chunk actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
90% confidence
Finding

The code is broadly aligned with a usage-tracking tool that uses user-supplied credentials from a secret store and avoids storing prompts. However, the declared description makes stronger privacy and scope claims than this code satisfies. In particular, the description says 'Only counts are stored — no prompts, no task titles,' but this module returns and is designed to retain task_id, status, started/completed timestamps, and metadata such as task_url and output_count. While it deliberately avoids reading/storing the prompt field, it stores more than just counts. Additionally, the estimate_credits function parses task description text to infer usage, which is a text-processing capability not obviously reflected in the declared description except perhaps loosely under transcript parsing; the description says such parsing is opt-in and off by default, but this code itself has no visible opt-in gate. So the description does not fully and accurately represent this code chunk's actual behavior.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
86% confidence
Finding

The code generally aligns with some safety-related claims in the description: credentials are obtained from a keychain-backed secret store, live provider probing is explicitly opt-in and off by default, and it does not appear to scrape arbitrary other applications’ credential stores. However, the declared description presents a broader product purpose—multi-provider tracking, budget alerts, and a local REST API dashboard—while this code chunk only implements an OpenAI-specific CLI fetcher for subscription caps and optional live rate-limit probing. Most importantly, the description says 'Only counts are stored — no prompts, no task titles,' but this script writes a JSON structure that includes more than counts, such as fetchedAt, provider, per-model status, model_used, detailed rate-limit header values, and plus_subscription_limits. That is not highly sensitive content, but it is still materially more than 'only counts.' Therefore the description does not accurately represent this code chunk.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
84% confidence
Finding

The skill explicitly supports reading generic provider API-key environment variables from other tools when TOKEN_PANEL_ALLOW_PROVIDER_ENV=1 is set. Even though this is opt-in and documented, it increases the chance of credential cross-use, accidental overreach, and exposure of secrets to a component beyond the original tool they were intended for. In a monitoring skill, credential ingestion is contextually expected, but borrowing other tools' secrets is still sensitive and broadens trust boundaries.

Content

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

md
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_ANTHROPIC_TOKEN / TOKEN_PANEL_ANTHROPIC_ADMIN_KEY / TOKEN_PANEL_OPENAI_KEY / TOKEN_PANEL_GEMINI_KEY / TOKEN_PANEL_MANUS_KEY, 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, each run by absolute path from /usr/bin or /bin — $PATH is never searched. Secrets are never passed as command-line arguments — macOS writes go through Security.framework, Linux writes go over stdin."

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
claude setup-token | python3 secretstore.py --login anthropic # scripts/claude-usage-fetch.py

Possible Typosquatting: 'uvicorn' resembles popular package 'gunicorn'

High
Category
Supply Chain
Confidence
70% confidence
Finding

Package name closely resembles a popular package, suggesting possible typosquatting. Attackers publish malicious packages with similar names to trick developers into installing them.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 11)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 109)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 169)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 173)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 174)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 178)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 179)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 180)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 181)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 182)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 194)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 196)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 201)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 202)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · secretstore.py (reported line 206)May include surrounding context.

python
1. Linux  — libsecret via `secret-tool` (the value is passed on STDIN, never in argv).
     The helper is taken from a fixed system directory (/usr/bin, /bin), never looked
     up on $PATH, so a `secret-tool` planted earlier on PATH cannot receive the secret.
  2. macOS  — the login Keychain through Security.framework (the value is passed as a
     pointer, never in argv). The `security` CLI is NOT used to write: its
     `add-generic-password -w <secret>` form puts the credential in this process's
     command line, where any local process enumerating processes can read it.

Credential Access

High
Category
Privilege Escalation
Confidence
82% confidence
Finding

The fallback path stores credentials in a plaintext file under the user's home directory when no keychain backend is available. Even with 0600 permissions, any code running as the same user can read the secrets, so compromise of the user context or a malicious local process exposes provider credentials. The skill context makes this somewhat less suspicious because the fallback is documented and warned about, but it remains a real confidentiality weakness.

Content

Scanner excerpt · secretstore.py (reported line 43)May include surrounding context.

python
SERVICE = "token-panel-ultimate"

# Where the fallback lives when the machine has no keychain at all.
FALLBACK_DIR = Path.home() / ".openclaw" / "data" / "token-panel"
FALLBACK_FILE = FALLBACK_DIR / "credentials"

Credential Access

High
Category
Privilege Escalation
Confidence
84% confidence
Finding

This warning string confirms the design choice to store credentials in a plain 0600 file if no OS keychain exists. That means secret confidentiality depends entirely on same-user isolation, which is weak on a compromised workstation or in multi-process user sessions. The context lowers suspicion because the user is warned, but warning alone does not eliminate the exposure.

Content

Scanner excerpt · secretstore.py (reported line 100)May include surrounding context.

python
}

FALLBACK_WARNING = (
    "WARNING: no OS keychain was found (neither `secret-tool` nor macOS `security`), so this\n"
    "         credential is stored in a plain 0600 file at:\n"
    "           {path}\n"
    "         Any process running as your user can read it. Install libsecret to use the\n"

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
tests/test_credentials.py:114