Back to skill

Security audit

Token Burn Monitor

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it claims, but it exposes sensitive agent activity through a local dashboard with under-scoped network and browser-safety controls.

Review before installing, especially on shared machines or any environment where localhost services may be proxied or exposed. Keep showPrompts disabled unless you intentionally want prompt excerpts visible, do not set BIND_HOST to a non-loopback address without adding authentication, and prefer pinned/verified installation sources. Treat custom themes and session/cron data as sensitive because the current default UI has unsafe HTML rendering paths.

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 (5)

T09 · Insecure Skill Coding Practices

Error
Location
themes/default/index.html:478
Finding

Persistent DOM XSS Through Unsanitized Monitoring Data

Content
View full analysis
{ const cfg = AGENTS[id] || { name: id }; return `
${cfg.icon ? `${cfg.name}` : `
${(cfg.name||id)[0]}
`}
${cfg.name}
${sortedModels.map(([m, d]) => { return ` ${m.split('/').pop()}: ${fmtCost(d.cost)} `; }).join('')}
`; }).join(''); ``` Other affected rendering paths include: ```javascript container.innerHTML = Object.entries(data.byAgent).map(([agentId, jobs]) => { return `
${cfg.name}
${jobs.map(job => `
${job.name || 'Unnamed'} ...[truncated 3342 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
server.js:500
Finding

Unauthenticated Sensitive API Can Be Exposed Beyond Localhost

Content
View full analysis
{ const url = new URL(req.url, `http://localhost:${PORT}`); if (req.method !== 'GET') { res.writeHead(405); res.end('Method not allowed'); return; } if (url.pathname === '/api/config') { const agentConfig = getAgentConfig(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ agents: agentConfig }, null, 2)); return; } if (url.pathname === '/api/history') { const days = parseInt(url.searchParams.get('days')) || 30; const history = getHistory(days); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ history }, null, 2)); return; } if (url.pathname === '/api/crons') { const jobs = getCronJobs(); // Cron metadata is returned without authentication. } if (url.pathname.startsWith('/api/agent/')) { const agentId = url.pathname.split('/')[3]; const date = url.searchParams.get('date') || getTodayStr(); const stats = getAgentStats(agentId, date); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(stats, null, 2)); return; } }); ``` ```javascript const BIND_HOST = process.env.BIND_HOST || '127.0.0.1'; server.listen(PORT, BIND_HOST, () => { const agentCount = Object.keys(getAgentConfig()).length; console.log(`🔥 Token Burn Monitor v5.3 running at http://${BIND_HOST}:${PORT}`); console.log(` Theme: ${CONFIG.theme} (${THEME_DIR})`); console.log(` Discovered ${agentCount} agents from ${AGENTS_DIR}`); }); ``` ### Technical Analysis The APIs expose information parsed from private agent session files and cron ...[truncated 2206 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
start.sh:5
Finding

Predictable Shared Temporary Files Enable Process Manipulation and File Clobbering

Content
View full analysis
"$LOG_FILE" 2>&1 & echo $! > "$PID_FILE" echo "🔥 Token Burn Monitor started (PID: $!)" echo " URL: http://localhost:$PORT" } stop() { if [ -f "$PID_FILE" ]; then kill $(cat "$PID_FILE") 2>/dev/null rm -f "$PID_FILE" echo "Token Burn Monitor stopped" else echo "Token Burn Monitor not running" fi } is_running() { if [ -f "$PID_FILE" ]; then if kill -0 $(cat "$PID_FILE") 2>/dev/null; then return 0 else rm -f "$PID_FILE" fi fi return 1 } ``` ### Technical Analysis The service-management script stores its PID and log in globally predictable `/tmp` paths. It does not create a private runtime directory, reject symbolic links, verify file ownership, or atomically create the files. The log redirection follows symbolic links and truncates the destination before launching Node.js. If the script is run by a more privileged account than the attacker, a pre-created symlink at `/tmp/token-burn-monitor.log` can cause another writable file to be truncated. The PID file is trusted without checking that: - Its contents are a single numeric PID. - The file is owned by the current user. - The file is not a symbolic link. - The referenced process is the expected `server.js` process. - The process belongs to the invoking user. Unquoted command substitution also allows malformed PID contents to be split into multiple arguments to `kill`. ### Attack Path #### Log-file clobbering 1. A loca ...[truncated 1187 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
server.js:516
Finding

Unbounded Synchronous History Processing Enables Denial of Service

Content
View full analysis
[id, { totalCost: a.totalCost, totalTokens: a.totalTokens, messageCount: a.messageCount }]) ) }); } return history.filter(d => d.totalCost > 0 || d.messageCount > 0); } ``` ```javascript if (url.pathname === '/api/history') { const days = parseInt(url.searchParams.get('days')) || 30; try { const history = getHistory(days); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ history }, null, 2)); } catch (e) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: e.message })); } return; } ``` The underlying parser performs synchronous file reads: ```javascript const content = fs.readFileSync(filePath, 'utf8'); ``` ### Technical Analysis The `days` query parameter is parsed as an integer but is not constrained to a safe range. `getHistory()` executes one full `getAllStats()` operation per requested day. Each operation discovers agents and synchronously rereads and reparses session JSONL files. Node.js handles requests on a single event loop. Synchronous filesystem operations and JSON parsing block all other requests unt ...[truncated 1345 chars]
Remediation
View remediation
30) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'days must be an integer from 1 to 30' })); return; } ``` 2. Cap the supported history range to the documented 30-day requirement. 3. Cache daily aggregates rather than reparsing all files for every date and request. 4. Avoid synchronous filesystem operations in request handlers. 5. Use worker threads or background indexing for large session datasets. 6. Add request timeouts, concurrency limits, and rate limiting. 7. Limit response size and avoid retaining unbounded arrays. 8. Add tests for zero, negative, nonnumeric, extremely large, and repeated `days` values. ]]>

T08 · Insecure Dependencies

Warning
Location
README.md:29
Finding

Unpinned npx Installation Commands Introduce Supply-Chain Code-Execution Risk

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Rogue AgentSelf-Modification, Session Persistence
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
Findings (14)

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
Themes live in `themes/<name>/`. Minimum: one `index.html` that fetches data from the API.

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

Scanner excerpt · themes/default/index.html (reported line 8)May include surrounding context.

html
<meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Token Burn</title>
  <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
  <!-- No external font loads — uses system fonts for zero network dependency -->
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }

YARA rule 'agent_skill_mcp_tool_poisoning_metadata': MCP/tool metadata poisoning indicators in tool schemas or skill manifests [agent_skills]

High
Category
YARA Match
Confidence
80% confidence
Finding

YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Content

Scanner excerpt · themes/default/index.html (reported line 8)May include surrounding context.

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Token Burn</title>
  <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
  <!-- No external font loads — uses system fonts for zero network dependency -->
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    
    :root {
      --bg-base: #050508;
      --bg-card: rgba(16, 18, 27, 0.5);
      --bg-card-hover: rgba(22, 25, 38, 0.7);
      --border: rgba(255, 255, 255, 0.08);
      --text-main: #f8f9fa;
      --text-muted: #8b92a5;
      --accent-cyan: #00f0ff;
      --accent-purple: #bd00ff;
      --accent-blue: #0057ff;
      --accent-amber: #ffaa00;
      --accent-green: #00ff88;
      --accent-red: #ff4466;
      --accent-coral: #ff6b6b;
      --

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The API explicitly documents that user prompts can be exposed when showPrompts is enabled, yet the warning is understated for data that may contain secrets, personal data, or proprietary content. Even though prompts are truncated and localhost-bound, local dashboards, shared workstations, port forwarding, reverse proxies, or accidental rebinding could disclose sensitive prompt content to unintended viewers.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
92% confidence
Finding

The README instructs users to run npx clawhub install token-burn-monitor without pinning an exact package version. This can cause users to fetch and execute whatever version is current at install time, creating a supply-chain risk if the upstream package is compromised or a breaking/malicious release is published later. Because this is an installation command, the context makes the risk more meaningful than a passive reference.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
90% confidence
Finding

The README recommends npx skills add KasparChen/burn-monitor-skill without pinning a version, tag, or commit. That means the resolved code may change over time, and a compromised upstream registry, account, or repository could result in execution of unintended code during install. In a skill-distribution context, this is a realistic supply-chain exposure.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding

This line repeats the unpinned npx clawhub install token-burn-monitor pattern in the Links section, reinforcing an unsafe installation path. Even though it is documentation rather than executable code in the repository itself, users commonly copy-paste such commands, so the supply-chain risk remains real. The repeated exposure slightly increases practical risk.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
88% confidence
Finding

This line repeats an unpinned npx skills add KasparChen/burn-monitor-skill install command. As with the earlier occurrence, users may execute a mutable upstream artifact, exposing them to package/repository compromise or unexpected future changes. The README's context as installation guidance makes this more dangerous than a casual mention.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The server exposes session-derived data, including user prompts in the /api/agent endpoint when SHOW_PROMPTS=1 or config.showPrompts=true. Although the service binds to localhost by default, localhost services are often reachable by other local users, browser-based localhost access, reverse proxies, or accidental rebinding via environment variables, so sensitive prompt content can be disclosed.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
65% 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 · start.sh (reported line 16)May include surrounding context.

sh
fi
    
    cd "$SCRIPT_DIR"
    nohup node server.js > "$LOG_FILE" 2>&1 &
    echo $! > "$PID_FILE"
    echo "🔥 Token Burn Monitor started (PID: $!)"
    echo "   URL: http://localhost:$PORT"

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The functions toCST and toFullCST hard-code zh-CN locale and Asia/Shanghai timezone for displayed timestamps. This imposes a specific language/locale behavior on all users without opt-in or an evident region-specific requirement, which matches the natural-language locale policy violation criteria.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

When timestamps are older than 24 hours, formatLastActive renders them using toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' ... }). This is another user-facing locale restriction embedded in the interface rather than an optional or documented choice.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
76% confidence
Finding

L005 states that no cross-origin requests are served, implying a constrained local-only exposure model. However, later documentation explicitly allows /api/agent/:id to return user prompt text when showPrompts is enabled, which weakens the implied privacy/safety posture of the API and makes the earlier claim materially misleading in intent.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

The cron description logic renders some schedules specifically in 'Beijing' time, such as 'Weekdays at ... Beijing', instead of using a neutral timezone or offering a locale/timezone choice. This is a natural-language locale policy issue because the skill imposes a specific regional framing on users without documented opt-in or justification in this file.

Content

No source excerpt is available for this finding.