Back to skill

Security audit

ai-quota-check

Security checks for vulnerabilities and agentic risk

Overview

The skill provides the promised quota dashboard, but it also reads local authentication/session data and silently makes an authenticated Codex request that can consume quota.

Install only if you are comfortable with the skill reading OpenClaw auth profiles, using provider tokens to query quota APIs, showing account emails in the response, and making a small authenticated Codex request whenever Codex quota is checked. Avoid unattended cron use unless you accept possible quota consumption and session activity.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
index.js:15
Finding
Overbroad Authentication Profile Access and Account Identifier Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `index.js:15`, `index.js:44-66`, and `index.js:334-343` **Vulnerability Type**: T05: Unauthorized Access and Privilege Escalation **Risk Level**: Medium ### Relevant Code ```js const AUTH_FILE = path.join(process.env.HOME, '.openclaw/agents/main/agent/auth-profiles.json'); ``` ```js function loadAuthProfiles() { if (!fs.existsSync(AUTH_FILE)) { return { profiles: {}, error: 'Auth file not found' }; } try { const data = JSON.parse(fs.readFileSync(AUTH_FILE, 'utf8')); return { profiles: data.profiles || {}, raw: data }; } catch (e) { return { profiles: {}, error: e.message }; } } function getLoggedInProviders(profiles) { const providers = {}; for (const [key, profile] of Object.entries(profiles)) { const provider = profile.provider; if (!providers[provider]) { providers[provider] = { loggedIn: true, email: profile.email || null, expires: profile.expires || null, isExpired: profile.expires ? Date.now() > profile.expires : false }; } } return providers; } ``` ```js const providerNames = ['google-antigravity', 'github-copilot', 'openai-codex']; for (const p of providerNames) { const info = loggedIn[p]; if (info) { const status = info.isExpired ? '⚠️ Token Expired' : '✅ Logged In'; console.log(`| ${p} | ${status} | ${info.email || '-'} |`); } else { console.log(`| ${p} | ❌ Not Logged In | - |`); } } ``` ### Technical Analysis The Skill reads and parses the complete shared OpenClaw authentication profile file even though its declared purpose only requires credentials and expiration information for three supported providers. It also returns the complete parsed object through the unused `raw` property, unnecessarily retaining all data from the authentication store. The dashboard includes provider account email addresses in its standard output. `SKILL.md` instructs the invoking Agent to reproduce this o ...[truncated 1715 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the unused `raw` return value so the complete authentication document is not retained: ```js return { profiles: data.profiles || {} }; ``` 2. Immediately filter profiles to the explicitly supported providers: - `google-antigravity` - `github-copilot` - `openai-codex` 3. Copy only required fields into short-lived objects rather than passing complete profile records through the program. 4. Do not display email addresses by default. Show a redacted identifier or `-`. 5. If account display is necessary, require an explicit option such as `--show-account`. 6. Document the authentication file being accessed and the specific fields used. 7. Run the Skill under an account or sandbox that cannot read unrelated authentication stores where possible. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
index.js:175
Finding
Implicit Authenticated Codex Request Consumes Quota and Creates Session Activity<![CDATA[ ## Vulnerability Details **File Location**: `index.js:175-190` and `index.js:192-203` **Vulnerability Type**: T05: Unauthorized Access and Privilege Escalation **Risk Level**: Medium ### Relevant Code ```js function pingCodexForFreshRateLimits() { try { // Same approach as odrobnik/codex-quota: make a tiny Codex request so the client writes // a new session event containing up-to-date rate_limits. execSync('codex exec --skip-git-repo-check "reply OK"', { cwd: process.env.HOME, stdio: 'ignore', timeout: 60_000 }); // Give the client a moment to flush the JSONL file. try { execSync('sleep 0.5', { stdio: 'ignore' }); } catch {} } catch { // Best-effort; we'll fall back to cached session data. } } function fetchCodexQuota() { const results = { primary: null, secondary: null, error: null, dataSource: null }; try { // Always refresh Codex rate limits by issuing a tiny Codex request first. // (Cached session JSONL can be stale if Codex hasn't been used recently.) pingCodexForFreshRateLimits(); ``` ### Technical Analysis Every normal Codex quota check invokes: ```bash codex exec --skip-git-repo-check "reply OK" ``` This is an authenticated model request, not a passive status query. It can consume quota, create provider-side activity, and write a new local Codex session event. The behavior is especially significant because `SKILL.md` states that the Skill is designed for periodic cron use, so repeated monitoring can repeatedly consume the resource being measured. The command is a fixed string and no command-injection path was found. However, automatic execution exceeds the minimum privilege and side effects expected of a dashboard that appears to perform quota inspection. The README and normal usage instructions do not clearly warn that each invocation may submit a real model request. ### Attack Path 1. A user invokes the dashboard directly, or a scheduler invokes it periodically. 2. ...[truncated 1127 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make cached, passive quota inspection the default behavior. 2. Require an explicit option such as `--refresh` before issuing a real Codex request. 3. Clearly warn that refresh mode submits an authenticated model request, consumes quota, and writes session data. 4. Add a minimum refresh interval and store only a timestamp of the last refresh to prevent excessive cron requests. 5. If Codex exposes a documented quota or status interface, use it instead of generating a model completion. 6. Avoid invoking a shell when possible. Use `execFileSync()` with a fixed executable and argument array: ```js execFileSync('codex', [ 'exec', '--skip-git-repo-check', 'reply OK' ], { cwd: process.env.HOME, stdio: 'ignore', timeout: 60_000 }); ``` 7. Replace the external `sleep` command with an asynchronous JavaScript timer. 8. Report whether quota data is cached or freshly retrieved so users can make an informed choice. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
index.js:153
Finding
Whole Codex Session File Read for Limited Rate-Limit Metadata<![CDATA[ ## Vulnerability Details **File Location**: `index.js:153-170` and `index.js:205-212` **Vulnerability Type**: T05: Unauthorized Access and Privilege Escalation **Risk Level**: Medium ### Relevant Code ```js function extractCodexRateLimitsFromSession(filePath) { const text = fs.readFileSync(filePath, 'utf8'); const lines = text.split(/\r?\n/); for (let i = lines.length - 1; i >= 0; i--) { const line = lines[i].trim(); if (!line) continue; try { const event = JSON.parse(line); const payload = event?.payload; if (payload?.type === 'token_count' && payload?.rate_limits) { return payload.rate_limits; } } catch { // ignore parse errors } } return null; } ``` ```js const latest = findLatestCodexSessionFile(); if (!latest) { results.error = 'No recent Codex session file found'; return results; } const limits = extractCodexRateLimitsFromSession(latest); if (!limits) { results.error = 'No rate_limits found in latest Codex session'; return results; } ``` ### Technical Analysis The Skill reads the entire newest Codex JSONL session into memory to retrieve one `rate_limits` object. Codex session files may contain prompts, responses, source excerpts, filesystem paths, tool activity, and other conversation metadata unrelated to quota monitoring. The current implementation does not print or transmit these session contents. Nevertheless, loading the complete file grants the process access to substantially more sensitive information than required. It also creates a denial-of-service concern if a selected session file is unexpectedly large, because the complete file is synchronously read and then duplicated into an array of lines. The declared quota-monitoring functionality only requires the most recent matching rate-limit event, not the complete conversation history. ### Attack Path 1. The Skill searches `~/.codex/sessions` for recent JSONL files. 2. It selects the newest session file ...[truncated 1144 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer a documented provider or Codex status interface that returns quota metadata without reading conversation history. 2. If session inspection is unavoidable, read only bounded tail blocks from the file rather than loading the entire file. 3. Parse and retain only events whose payload type is `token_count`; immediately discard all other records. 4. Enforce a conservative maximum file size or maximum number of bytes scanned. 5. Use asynchronous or streaming I/O to avoid blocking the process and duplicating a large file in memory. 6. Validate that the selected path remains inside the expected session directory before opening it. 7. Document that Codex session metadata is accessed and explain exactly which event fields are used. 8. Consider maintaining a separate minimal quota cache containing only rate-limit fields and timestamps. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (11)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding
The declared purpose is passive quota checking, but the described behavior includes live command execution, reading local auth/session material, and making network requests with stored tokens. That mismatch is dangerous because users may invoke the skill expecting simple reporting while it actually performs privileged actions that can expose credentials, query external services, or trigger side effects.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
Unified quota monitor and intelligent model recommender for all providers.

## Output Instructions

**IMPORTANT:** When executing this skill, display the script output **EXACTLY as-is** in markdown format. Do NOT summarize or rephrase the output. The script produces a formatted dashboard that should be shown directly to the user.
Confidence
91% confidence
Finding
The instruction to display script output exactly as-is creates a direct prompt/output passthrough channel. If the underlying script emits sensitive data, embedded instructions, or adversarial content sourced from provider responses, logs, environment-derived values, or local files, the agent is told not to sanitize or reinterpret it before sending it to the user.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The quota-check flow spawns `codex exec --skip-git-repo-check "reply OK"`, which causes a real outbound Codex request as a side effect of merely checking quota status. In this context, a monitoring skill should not silently consume paid usage, contact an external service, or trigger model execution without explicit consent, especially because it may transmit workspace or environment-derived context through the CLI path.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The trigger list includes very generic terms such as "quota" and common Korean variants, which can cause the skill to activate in unrelated conversations where a user casually mentions quotas. Because this is marked as a default skill to use first, over-broad invocation increases the chance of unintended execution, misrouting, or disclosure of provider/account quota information when the user did not explicitly request this skill.

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill advertises executable behavior and environment access via required binaries, but it does not declare an explicit tool scope such as permissions or allowed-tools. That weakens containment and reviewability, making it easier for a quota-check skill to access environment-derived secrets or runtime capabilities beyond what a user would reasonably expect.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The trigger is very broad, telling the agent to use this skill first for generic quota-related requests. In context, that increases risk because a widely auto-invoked skill appears to perform sensitive local-state inspection and external provider interactions, so it may activate in more situations than necessary without clear boundaries or consent.

Description-Behavior Mismatch

Medium
Confidence
96% confidence
Finding
The manifest presents this as a default quota checker that users should invoke to view provider quotas. The code additionally embeds task-specific routing rules and later emits prescriptive model recommendations, which is a broader decision-making function than simple quota inspection.

External Transmission

Medium
Category
Data Exfiltration
Content
};

  try {
    const data = await fetchJson('https://api.github.com/copilot_internal/user', headers, null, 'GET');
    const premium = data.quota_snapshots?.premium_interactions;
    
    if (premium) {
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The subprocess invocation triggers a live Codex request with no user-facing disclosure or confirmation. In a quota-checking skill, hidden side effects are especially risky because users reasonably expect read-only behavior; instead, this can silently spend quota, generate logs/session artifacts, and create unintended external traffic.

Intent-Code Divergence

Medium
Confidence
91% confidence
Finding
The comments suggest the implementation is not calling a quota endpoint, but the code still forces a live Codex request before reading limits. That mismatch is dangerous because it obscures the true behavior of the skill, undermines informed consent, and makes reviewers or users more likely to trust a flow that actually performs networked side effects.

Missing User Warnings

Low
Confidence
79% confidence
Finding
The script loads `auth-profiles.json` from the user's home directory and processes provider account details such as email, expiry, and tokens, but there is no user-facing warning that sensitive local authentication metadata is being accessed. Developer comments and variable names are present, but the criteria require some form of disclosure to the user for sensitive credential access.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
index.js:205