Back to skill

Security audit

API Credit Health Bar Lite

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a disclosed API balance tracker, but its OpenAI auto-check asks for an organization-admin API key, which is broader access than users may expect.

Install only if you are comfortable with local balance tracking and provider API calls. Prefer manual sync or narrowly scoped billing/read-only keys where available, avoid exposing an OpenAI organization-admin key, and use a pinned/isolated Python environment before enabling auto-checks.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

T08 · Insecure Dependencies

Warning
Location
README.md:60
Finding
Unpinned Third-Party Runtime Dependencies<![CDATA[ ## Vulnerability Details **File Location**: `README.md:60-64`, `scripts/check_openai.py:1`, `scripts/check_openrouter.py:1`, `scripts/check_vercel.py:1` **Vulnerability Type**: Supply-chain exposure through unpinned Python dependencies **Risk Level**: Medium ### Vulnerable Code ```markdown Optional: install `requests` for API auto-checks: ```bash pip install requests ``` ``` The runtime scripts also direct users to install packages without version or integrity constraints: ```python try: from openai import OpenAI except ImportError: print("❌ OpenAI library not installed. Install with: pip install openai") sys.exit(1) ``` ```python try: import requests except ImportError: print("❌ requests library not installed. Install with: pip install requests") sys.exit(1) ``` ### Technical Analysis The project does not provide a pinned `requirements.txt`, lockfile, package hashes, or an explicitly trusted package index. Users are instructed to install the latest available versions of `requests` and `openai`. This does not prove that either named package is malicious. However, it means the reviewed source does not fully determine the code that executes at runtime. A future compromised, malicious, or behaviorally incompatible dependency release could run inside the same Python process and access the process environment, filesystem permissions, and network privileges. This exposure is especially relevant because the scripts execute while provider credentials such as `OPENAI_API_KEY`, `OPENROUTER_API_KEY`, and `VERCEL_AI_GATEWAY_KEY` may be present in environment variables. ### Attack Path 1. A user enables an automatic balance checker. 2. The user follows the documentation or runtime message and executes `pip install requests` or `pip install openai`. 3. Package resolution selects the latest release because no version or hash is specified. 4. If the selected package or one of its transitive dependencies has been compromised, its ini ...[truncated 975 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add a reviewed dependency manifest with exact versions, for example: ```text openai==<reviewed-version> requests==<reviewed-version> ``` 2. Generate and verify cryptographic hashes, using a workflow such as: ```bash pip-compile --generate-hashes requirements.in pip install --require-hashes -r requirements.txt ``` 3. Pin transitive dependencies through a lockfile rather than pinning only direct dependencies. 4. Use an explicitly trusted package index and disable unexpected fallback indexes where practical: ```bash python3 -m pip install \ --index-url https://pypi.org/simple \ --require-hashes \ -r requirements.txt ``` 5. Run dependency vulnerability and provenance checks during releases. 6. Test and review dependency upgrades before updating the lockfile. 7. Execute automatic checks in a restricted environment containing only the single credential required for that provider. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/check_openai.py:1
Finding
OpenAI Balance Checker Requests an Organization-Administrator Credential<![CDATA[ ## Vulnerability Details **File Location**: `scripts/check_openai.py:1` **Vulnerability Type**: Excessive credential privilege **Risk Level**: Medium The file is physically stored as one line containing escaped newline sequences, so its pinpointed source location is line 1. The equivalent root-level duplicate is also located at `check_openai.py:1`. ### Vulnerable Code ```python """ Check OpenAI credits balance via API. Requires: OPENAI_API_KEY environment variable (org admin key, not user key) Usage: OPENAI_API_KEY=sk-... python3 check_openai.py OPENAI_API_KEY=sk-... python3 check_openai.py --update """ ``` The credential is passed to the OpenAI SDK for a billing query: ```python def check_openai_balance(api_key): """ Check OpenAI balance using API. Requires: Organization admin API key (not user key) Returns: (total_balance, date) """ try: client = OpenAI(api_key=api_key, organization=None) # Get billing info billing_info = client.beta.billing.credit_grants.list() total_balance = 0 for grant in billing_info.data: if hasattr(grant, 'balance'): total_balance += float(grant.balance) return total_balance, datetime.now().isoformat() except Exception as e: print(f"❌ Failed to check OpenAI balance: {str(e)}") print("\n💡 Note: This requires an organization admin API key, not a user key.") print(" Get it from: https://platform.openai.com/account/api-keys (select org)") return None, None ``` The key is obtained from the process environment: ```python if __name__ == "__main__": api_key = os.getenv('OPENAI_API_KEY') if not api_key: print("❌ OPENAI_API_KEY environment variable not set") sys.exit(1) update = '--update' in sys.argv sys.exit(check_and_display(api_key, update=update)) ``` ### Technical Analysis The checker explicitly requests an OpenAI organization-administra ...[truncated 2247 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace the organization-administrator requirement with the narrowest credential supported by the provider, preferably a billing-read-only or project-scoped credential. 2. If the required billing endpoint cannot operate with a least-privileged key, disable the automatic OpenAI checker by default and document that limitation prominently. 3. Add an explicit warning before execution: ```text This operation requires an organization-level administrative credential. Do not continue unless no read-only alternative is available. Use manual balance synchronization when possible. ``` 4. Align `scripts/check_openai.py`, `SKILL.md`, `README.md`, and `SECURITY.md` so they provide consistent credential guidance. 5. Run the checker in an isolated process with a minimal environment containing only `OPENAI_API_KEY` and necessary system variables. 6. Avoid exporting the administrator key globally. Inject it only for the duration of a single command and remove it immediately afterward. 7. Pin and verify the OpenAI SDK and its transitive dependencies. 8. Document key rotation and audit-log review procedures for users who enable this feature. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (13)

Credential Access

High
Category
Privilege Escalation
Content
*.egg

# Virtual environments
.env
.venv
env/
venv/
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The README advertises automated API balance pulls but does not warn users that enabling this feature involves using provider credentials and making outbound network requests. That omission can mislead users about the sensitivity of the operation and increases the risk of unintentionally granting a skill access to billing-related APIs.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The README tells users they can invoke the skill with broad, everyday phrases such as asking how much credit is left or whether anything is running low. In agent environments, vague natural-language triggers can cause accidental activation during unrelated conversation, which is more concerning here because the skill handles account balance data and may initiate balance updates or checks.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The README lists environment variables for provider API keys without accompanying guidance on secure handling, least-privilege use, storage, rotation, or exposure risks. In agent skill ecosystems, this can normalize casually supplying sensitive credentials to a skill without understanding how they will be used or protected.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
```bash
# Read/write for owner only
chmod 600 config.json

# View permissions
ls -l config.json
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Session Persistence

Medium
Category
Rogue Agent
Content
- VERCEL_AI_GATEWAY_KEY
permissions:
  - network: Contact OpenAI, OpenRouter, and Vercel APIs to check balances (optional)
  - filesystem: Read/write config.json and health bar display
---

# API Credits Lite
Confidence
82% confidence
Finding
The skill explicitly persists account balance information by reading and writing config.json, creating a session-persistence/privacy risk if financial usage data remains on disk longer than the user expects. While the stored data appears limited to provider balances rather than secrets, local persistence can expose spending patterns or account state to other local users, backups, or later processes.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The module docstring describes the script as a balance checker and its primary usage examples emphasize checking credits balance. However, the implementation later supports an --update mode that writes provider data back to ../config.json, which is a state-changing side effect not reflected in the stated behavior.

External Transmission

Medium
Category
Data Exfiltration
Content
#!/usr/bin/env python3\n\"\"\"\nCheck Vercel AI Gateway balance via API.\n\nRequires: VERCEL_AI_GATEWAY_KEY environment variable\n\nUsage:\n    VERCEL_AI_GATEWAY_KEY=... python3 check_vercel.py\n    VERCEL_AI_GATEWAY_KEY=... python3 check_vercel.py --update\n\"\"\"\n\nimport os\nimport json\nimport sys\nfrom datetime import datetime\n\ntry:\n    import requests\nexcept ImportError:\n    print(\"❌ requests library not installed. Install with: pip install requests\")\n    sys.exit(1)\n\ndef load_config():\n    \"\"\"Load config from the skill directory\"\"\"\n    script_dir = os.path.dirname(os.path.abspath(__file__))\n    config_path = os.path.join(script_dir, '..', 'config.json')\n    \n    try:\n        with open(config_path, 'r') as f:\n            return json.load(f)\n    except FileNotFoundError:\n        return {'providers': {}, 'thresholds': {'warning': 50, 'critical': 25}}\n\ndef save_config(config):\n    \"\"\"Save config back to file\"\"\"\n    script_dir = os.path.dirname(os.path.abspath(__file__))\n    config_path = os.path.join(script_dir, '..', 'config.json')\n    \n    with open(config_path, 'w') as f:\n        json.dump(config, f, indent=2)\n\ndef check_vercel_balance(api_key):\n    \"\"\"\n    Check Vercel AI Gateway balance using API.\n    \n    Returns: (balance, limit, timestamp)\n    \"\"\"\n    try:\n        response = requests.get(\n            'https://api.vercel.com/v1/billing',\n            headers={\n                'Authorization': f'Bearer {api_key}',\n                'Content-Type': 'application/json'\n            }\n        )\n        \n        if response.status_code != 200:\n            print(f\"❌ API error: {response.status_code}\")\n            if response.status_code == 401:\n                print(\"   Invalid API key\")\n            return None, None, None\n        \n        data = response.json()\n        \n        # Vercel returns balance and usage info\n        balance = float(data.get('balance', 0))\n        limit = float(data
...[truncated 26 chars]
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
#!/usr/bin/env python3\n\"\"\"\nCheck Vercel AI Gateway balance via API.\n\nRequires: VERCEL_AI_GATEWAY_KEY environment variable\n\nUsage:\n    VERCEL_AI_GATEWAY_KEY=... python3 check_vercel.py\n    VERCEL_AI_GATEWAY_KEY=... python3 check_vercel.py --update\n\"\"\"\n\nimport os\nimport json\nimport sys\nfrom datetime import datetime\n\ntry:\n    import requests\nexcept ImportError:\n    print(\"❌ requests library not installed. Install with: pip install requests\")\n    sys.exit(1)\n\ndef load_config():\n    \"\"\"Load config from the skill directory\"\"\"\n    script_dir = os.path.dirname(os.path.abspath(__file__))\n    config_path = os.path.join(script_dir, '..', 'config.json')\n    \n    try:\n        with open(config_path, 'r') as f:\n            return json.load(f)\n    except FileNotFoundError:\n        return {'providers': {}, 'thresholds': {'warning': 50, 'critical': 25}}\n\ndef save_config(config):\n    \"\"\"Save config back to file\"\"\"\n    script_dir = os.path.dirname(os.path.abspath(__file__))\n    config_path = os.path.join(script_dir, '..', 'config.json')\n    \n    with open(config_path, 'w') as f:\n        json.dump(config, f, indent=2)\n\ndef check_vercel_balance(api_key):\n    \"\"\"\n    Check Vercel AI Gateway balance using API.\n    \n    Returns: (balance, limit, timestamp)\n    \"\"\"\n    try:\n        response = requests.get(\n            'https://api.vercel.com/v1/billing',\n            headers={\n                'Authorization': f'Bearer {api_key}',\n                'Content-Type': 'application/json'\n            }\n        )\n        \n        if response.status_code != 200:\n            print(f\"❌ API error: {response.status_code}\")\n            if response.status_code == 401:\n                print(\"   Invalid API key\")\n            return None, None, None\n        \n        data = response.json()\n        \n        # Vercel returns balance and usage info\n        balance = float(data.get('balance', 0))\n        limit = float(data
...[truncated 26 chars]
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Intent-Code Divergence

Medium
Confidence
94% confidence
Finding
The top-level docstring says 'Check Vercel AI Gateway balance via API' and its usage examples frame the script as a checker. However, the implementation supports an '--update' mode that writes provider state back into ../config.json, which is a meaningful side effect not reflected in the stated intent.

Missing User Warnings

Low
Confidence
88% confidence
Finding
This markdown file documents network calls to provider APIs and read/write access to config.json, but it does not include a user-facing warning about privacy or local data modification effects. Because the skill can contact external services and persist balance information, the description should more explicitly disclose those behaviors before use.

Missing User Warnings

Low
Confidence
86% confidence
Finding
This Python file performs a file write to config.json in save_config(), and the user-facing usage text only mentions the --update flag without clearly warning that it will modify the local configuration file. Although the function names and comments imply updating config, there is no explicit disclosure at invocation time beyond a generic 'Config updated' message after the write occurs.

Missing User Warnings

Low
Confidence
89% confidence
Finding
This code overwrites a persistent config file via save_config(), but the script provides no user-facing notice at the time of the write beyond returning JSON output afterward. While the module docstring implies balance updates, there is no explicit warning in comments, help text, or runtime output that running the command will modify ../config.json.

Static analysis

No suspicious patterns detected.