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. ]]>
