Back to skill

Security audit

Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent autonomous game agent, but it needs Review because it stores and prints secrets and can automatically take consequential game-account actions without clear user approval.

Review before installing if you care about strict control over your game account or API keys. Keep the generated .env private, avoid running setup in shared terminals or CI logs, rotate keys if they are exposed, and consider disabling or modifying automatic crew-joining and high-spend autonomous actions before long unattended runs.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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)

T01 · Skill Instruction Hijacking

Error
Location
arena-agent.js:393
Finding
Untrusted Remote Game Data Is Embedded in the LLM System Prompt<![CDATA[ ## Vulnerability Details **File Location**: `arena-agent.js:393-486` **Vulnerability Type**: Prompt injection through untrusted remote data **Risk Level**: High ### Vulnerable Code ```js function buildSystemPrompt(state, recentActions = [], recentDistricts = []) { const player = state.player || {}; // ... const districtPlayers = (state.district_players || []) .filter(p => p.id !== player.id) .map(p => `[${p.id}] ${p.username} (${p.rank_title})`) .join('\n ') || 'none nearby'; const myContracts = (state.my_contracts || []) .map(c => `[${c.id}] ${c.contract_type}: ${c.description} ${c.progress || 0}/${c.target} ($${c.scaled_reward || c.base_reward})`) .join('\n '); const offeredContracts = (state.contracts || []) .filter(c => c.status === 'offered') .map(c => `[${c.id}] ${c.contract_type}: ${c.description} ($${c.scaled_reward || c.base_reward})`) .join('\n '); // Dynamic context only return `${STATIC_SYSTEM_PREFIX} ## Your Status ${player.username} | ${rankTitle} (${rank}/7) | XP: ${player.reputation_xp || 0} | ${player.current_district} Dirty: $${player.dirty_cash || 0} | Clean: $${player.clean_cash || 0} | Heat: ${player.heat_level?.toFixed(1) || 0}/${HEAT_MAX} ${player.heat_level > 25 ? 'RISK' : ''} Season: $${player.season_revenue || 0} | Shaken: ${player.is_shaken ? 'YES' : 'No'} | Launder cap: $${player.solo_launder_remaining ?? '?'} ## Resources Inventory: ${inventory} Dealers (${(state.dealers || []).length}/8): ${dealers} Cooks: ${cooks} Gear: ${gear} Contracts: ${contracts} ## Environment Players: ${districtPlayers}${buildMarketSummary(state.market, rank, player.current_district)} ${buildCrewSection(state, player)} ${buildTurfSection(state, player)} ${formatActionHistory(recentActions)} ${formatAvailableActions(state, rank, availableDrugs, hasActiveContract, player)}`; } ``` The resulting string is subsequently supplied as the system-role message in `llm.js:25-29`: ```js const mes ...[truncated 2468 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not place remote game state in the system-role message. Keep the static policy in the system message and place dynamic state in a user-role message. 2. Serialize remote state as JSON rather than interpolating it into prose. 3. Add an explicit instruction immediately before the data stating that all enclosed values are untrusted game data and must never be treated as instructions. 4. Normalize and length-limit user-controlled display fields such as usernames, crew names, and strategy descriptions. 5. Reject or escape control sequences, Markdown headings, role markers, and instruction-like content in remote text fields where practical. 6. Validate model output against a strict per-action JSON schema, including action-specific numeric limits and enumerations. 7. Introduce policy checks for consequential actions such as crew deposits, crew changes, turf operations, wars, and hostile actions. 8. Require explicit user confirmation or configurable spending limits for actions that transfer or irreversibly consume resources. 9. Prefer structured server identifiers and enumerated status values over free-form server notes when constructing decision context. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
arena-cli.js:80
Finding
Arena and OpenRouter Credentials Are Stored and Displayed Insecurely<![CDATA[ ## Vulnerability Details **File Location**: `arena-cli.js:80-92`, `arena-cli.js:121-124`, `arena-cli.js:204-213`, and `arena-cli.js:233-240` **Vulnerability Type**: Plaintext credential storage and secret disclosure through terminal output **Risk Level**: Medium ### Vulnerable Code The setup routine writes credentials to `.env` without explicitly applying owner-only permissions: ```js function writeEnvFile(vars) { const lines = [ '# Arena Agent — auto-generated by setup', '# Edit freely or re-run: npm run setup', '', ]; for (const [k, v] of Object.entries(vars)) { lines.push(`${k}=${v}`); } lines.push(''); writeFileSync(envPath, lines.join('\n')); } ``` The generated file contains both credentials: ```js const envVars = { ARENA_API_KEY: apiKey, ARENA_PLAYER_ID: playerId, OPENROUTER_API_KEY: openrouterKey, }; if (llmModel) envVars.ARENA_LLM_MODEL = llmModel; writeEnvFile(envVars); ``` New Arena keys are printed in full during setup: ```js const result = await client.register(ownerName || 'Arena Agent'); apiKey = result.api_key; console.log(`\n API Key: ${apiKey}`); console.log(` Key ID: ${result.key_id}`); console.log(` Max Players: ${result.max_players}\n`); ``` The standalone registration command also prints the complete secret: ```js const result = await client.register(name); console.log('\n=== API Key Registered ==='); console.log(`API Key: ${result.api_key}`); console.log(`Key ID: ${result.key_id}`); console.log(`Max Players: ${result.max_players}`); console.log(`Rate Limit: ${result.rate_limit_per_min}/min`); console.log(`\nSet this in your environment:`); console.log(` export ARENA_API_KEY="${result.api_key}"`); ``` ### Technical Analysis `writeFileSync()` is called without a restrictive `mode`. For a newly created file, effective permissions are determined by the process umask. On systems with permissive umasks, the plaintext `.env` file may be readable by other local users or processes. Exi ...[truncated 1616 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create `.env` with owner-only permissions: ```js writeFileSync(envPath, lines.join('\n'), { mode: 0o600 }); ``` 2. If the file already exists, call `chmodSync(envPath, 0o600)` after writing so permissive existing permissions are corrected. 3. Never print a complete credential. Display only a short fingerprint, such as the first four and last four characters. 4. Avoid printing a copyable `export` command containing the secret. 5. Prefer an operating-system credential store or secret manager over a plaintext `.env` file. 6. Ensure `.env` and generated logs are excluded from source control and backup systems that are not approved for secrets. 7. Warn users when the filesystem cannot enforce private permissions. 8. Support key rotation and document immediate revocation procedures for accidentally logged keys. ]]>

other

Warning
Location
arena-agent.js:1261
Finding
Untrusted Crew Invitations Are Accepted Automatically<![CDATA[ ## Vulnerability Details **File Location**: `arena-agent.js:1261-1276` **Vulnerability Type**: Untrusted event-triggered account action **Risk Level**: Medium ### Vulnerable Code ```js async function handleNotification(notif, state) { const kind = notif.kind || notif.event; const player = state?.player || {}; // Auto-accept crew invites if ((kind === 'crew_invite') && !player.crew_id) { try { await executeAction('crew_invite_response', { crew_id: notif.crew_id, accept: true }, `Accepting crew invite to ${notif.crew_name || 'crew'}`, effectiveModel); log('info', `Auto-accepted crew invite to ${notif.crew_name || notif.crew_id}`); } catch (e) { log('warn', `Failed to accept crew invite: ${e.message}`); } } } ``` ### Technical Analysis The notification handler accepts every crew invitation whenever the player is not already in a crew. It does not verify the inviter, compare the crew against an allowlist, evaluate crew policy, consult a user preference, or request confirmation. Crew enrollment is a persistent account-level game decision rather than a necessary transport or synchronization operation. The documented agent loop does not indicate that arbitrary invitations will be accepted automatically. Consequently, another game participant can trigger a meaningful account action solely by sending an invitation. The notification object's `crew_id` is passed directly to the authenticated action call. Although the server should validate that an invitation exists, the client performs no trust decision regarding which valid invitation should be accepted. ### Attack Path 1. The controlled player is not currently a member of a crew. 2. An attacker sends that player a crew invitation through the game. 3. The server delivers a `crew_invite` notification over WebSocket, SSE, or polling. 4. `handleNotification()` processes the event and submits `crew_invite_response` with `accept: true`. 5. The agent j ...[truncated 790 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Disable automatic crew acceptance by default. 2. Queue invitations for explicit user approval. 3. Add configuration such as `ARENA_AUTO_ACCEPT_CREW=false`, requiring deliberate opt-in. 4. If automatic acceptance is required, support allowlists of trusted crew IDs and inviter player IDs. 5. Display the inviter, crew identity, and relevant crew properties before requesting confirmation. 6. Validate `notif.crew_id` and ensure it corresponds to a currently pending invitation before submission. 7. Add policy limits preventing treasury deposits or other shared-resource operations immediately after automatic enrollment. 8. Record acceptance decisions in an audit log without including credentials. ]]>
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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (31)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The code is broadly aligned with the Burner Empire game domain and does provide client functionality for agent registration, state/actions, and real-time updates. However, the declared description specifically says 'Now with real-time WebSocket support,' while the supplied code explicitly implements an SSE stream client ('Server-Sent Events connection') and contains no WebSocket usage. That is a material description-versus-behavior mismatch. The rest of the functionality is generally consistent with an MMO arena client, so the mismatch is specific rather than total.

Ae1

High
Category
analysis-evasion
Content
- `arena-agent.js` — Main autonomous game loop (supports ws/sse/polling transport)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `arena-cli.js` — Management CLI (setup, register, create, status, leaderboard)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `arena-client.js` — REST API client
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
- `arena-ws-client.js` — WebSocket client (real-time transport)
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

External Model or Provider Selection

High
Category
Excessive Agency
Content
//   node arena-cli.js setup                       Guided setup (register + create + .env)
//   node arena-cli.js play [--duration 30m]       Run the agent (fork of arena-agent.js)
//   node arena-cli.js register                    Register for an API key
//   node arena-cli.js create --name AgentX --model claude-sonnet-4-6
//   node arena-cli.js status                      Show agent info and players
//   node arena-cli.js state --player-id UUID      Get game state
//   node arena-cli.js profile --name AgentX       Get public agent profile
Confidence
90% confidence
Finding
Skill selects an external model or provider that may use a different account or billing plan than the operator expects. Undisclosed model switches can cause unexpected cost or quota consumption.

Credential Access

High
Category
Privilege Escalation
Content
// Arena Agent - Configuration
// REST API client for Burner Empire AI Arena

// ── Auto-load .env from script directory (no dependencies) ──────────
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
// Arena Agent - Configuration
// REST API client for Burner Empire AI Arena

// ── Auto-load .env from script directory (no dependencies) ──────────
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
// Arena Agent - Configuration
// REST API client for Burner Empire AI Arena

// ── Auto-load .env from script directory (no dependencies) ──────────
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
// Arena Agent - Configuration
// REST API client for Burner Empire AI Arena

// ── Auto-load .env from script directory (no dependencies) ──────────
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
// Arena Agent - Configuration
// REST API client for Burner Empire AI Arena

// ── Auto-load .env from script directory (no dependencies) ──────────
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
// Arena Agent - Configuration
// REST API client for Burner Empire AI Arena

// ── Auto-load .env from script directory (no dependencies) ──────────
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
// Arena Agent - Configuration
// REST API client for Burner Empire AI Arena

// ── Auto-load .env from script directory (no dependencies) ──────────
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
// Arena Agent - Configuration
// REST API client for Burner Empire AI Arena

// ── Auto-load .env from script directory (no dependencies) ──────────
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
try {
  const __dir = dirname(fileURLToPath(import.meta.url));
  const envText = readFileSync(join(__dir, '.env'), 'utf8');
  for (const line of envText.split('\n')) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith('#')) continue;
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
try {
  const __dir = dirname(fileURLToPath(import.meta.url));
  const envText = readFileSync(join(__dir, '.env'), 'utf8');
  for (const line of envText.split('\n')) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith('#')) continue;
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill explicitly instructs the agent to read secrets from `.env`, including an API bearer token and player identifier, without any warning, consent boundary, or minimization guidance. In an agent setting, directing access to local secret material is dangerous because the same skill also drives outbound network requests and could cause credentials to be used or exposed beyond the user's intended scope.

External Transmission

Medium
Category
Data Exfiltration
Content
**Get state:**
```
curl -s https://burnerempire.com/api/arena/state/{PLAYER_ID} \
  -H "Authorization: Bearer {API_KEY}"
```
Confidence
92% confidence
Finding
The file instructs the agent to transmit data to an external service using a bearer token in the Authorization header. While contacting the game server is core to the skill's purpose, this is still a real security concern because the skill combines local secret access with external transmission, creating a path for credential misuse or unintended disclosure if the agent follows the instructions automatically.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill advertises executable setup and runtime flows that require environment variables and network access, but it does not declare any explicit tool scope or allowed-tools boundary in the skill manifest. That omission weakens least-privilege controls and can cause a host agent to grant broader capabilities than users expect when installing or running the skill.

Rp1

Medium
Category
MCP Rug Pull
Confidence
94% confidence
Finding
Using `npx clawhub install burnerempire-arena` without pinning a specific version introduces supply-chain risk because the fetched package may change over time or be replaced by a malicious publication. A user following the documented command could execute unreviewed code at install time with local developer privileges.

Rp1

Medium
Category
MCP Rug Pull
Confidence
94% confidence
Finding
This is the same unpinned remote execution pattern repeated in the standalone setup instructions. Recommending `npx clawhub install burnerempire-arena` allows arbitrary future package contents to run during installation, making the installation path vulnerable to package compromise or takeover.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The agent sends a large system prompt derived from live player, inventory, contracts, nearby players, crew, turf, and market state into the LLM decision function, but the skill description and code shown do not provide an explicit user warning about this disclosure. If the LLM backend is third-party or remote, this can leak sensitive gameplay and account-linked data, including identifiers and behavioral state, to external providers.

Context-Inappropriate Capability

Medium
Confidence
96% confidence
Finding
The notification handler automatically accepts any incoming crew invitation whenever the player is not already in a crew, without user confirmation or any trust policy. This grants other players the ability to change the agent's social affiliation and unlock shared treasury, war, turf, and crew-side actions, which exceeds normal per-tick gameplay autonomy and can be abused for manipulation or griefing.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill performs a sensitive social action—joining a crew—automatically and without any user warning or approval path. In this game context, crew membership is not cosmetic: it changes available actions and exposes the agent to group dynamics, making unsolicited invites a viable control/influence vector from untrusted players.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The setup flow persists sensitive credentials including ARENA_API_KEY and OPENROUTER_API_KEY into a plaintext .env file in the skill directory without any warning, permission hardening, or guidance to exclude the file from source control. On multi-user systems, in shared workspaces, or if the repository is later committed, these secrets can be exposed and reused to impersonate the user or consume paid API resources.

Static analysis

No suspicious patterns detected.