T09 · Insecure Skill Coding Practices
Error
- Location
- api/static/index.html:85
- Finding
- Stored DOM XSS Through Untrusted Token Metadata<![CDATA[ ## Vulnerability Details **File Location**: `api/static/index.html:85-119` **Vulnerability Type**: DOM-based cross-site scripting through unsafe HTML rendering **Risk Level**: High ### Vulnerable Code ```javascript res.innerHTML = ` <div class="score-ring"> <div class="ring-wrap"> <svg class="ring" width="130" height="130" viewBox="0 0 130 130"> <circle class="ring-bg" cx="65" cy="65" r="54"/> <circle class="ring-fill" cx="65" cy="65" r="54" stroke="${col}" stroke-dasharray="${circ}" stroke-dashoffset="${offset}"/> </svg> <div class="ring-num" style="color:${col}">${score}</div> </div> <div class="verdict ${d.verdict}">${d.verdict === 'SNIPE' ? '🎯 SNIPE' : d.verdict === 'CAUTION' ? '⚠️ CAUTION' : '🚨 AVOID'}</div> </div> <div class="token-bar"> <div class="ti"><span class="tl">Token</span><span class="tv">$${d.token.symbol} — ${d.token.name}</span></div> <div class="ti"><span class="tl">Price</span><span class="tv">$${parseFloat(d.token.price_usd||0).toPrecision(4)}</span></div> <div class="ti"><span class="tl">Liquidity</span><span class="tv">$${Number(d.token.liquidity_usd||0).toLocaleString()}</span></div> <div class="ti"><span class="tl">Market Cap</span><span class="tv">$${Number(d.token.market_cap||0).toLocaleString()}</span></div> </div> <div class="breakdown"> <h3>Score Breakdown</h3> ${Object.entries(d.breakdown).map(([k,v])=>` <div class="bar-row"> <span class="bar-label">${KEYS[k]||k}</span> <div class="bar-track"><div class="bar-fill" style="width:${(v.score/v.max*100)}%;background:${v.score/v.max>0.6?'#14f195':v.score/v.max>0.35?'#e3a008':'#f85149'}"></div></div> <span class="bar-num">${v.score}/${v.max}</span> </div>`).join('')} </div> <div class="signals"> <h3>Signals</h3> ${d.signals.map(s=>`<div class="signal">${s}</div>`).join('')} </div>`; ``` The values are populated f ...[truncated 2938 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not render externally sourced metadata through `innerHTML`. 2. Create DOM elements and assign untrusted values through `textContent`: ```javascript const tokenValue = document.createElement('span'); tokenValue.className = 'tv'; tokenValue.textContent = `$${d.token.symbol} — ${d.token.name}`; ``` 3. Construct signal rows individually: ```javascript for (const signal of d.signals) { const row = document.createElement('div'); row.className = 'signal'; row.textContent = String(signal); signalsContainer.appendChild(row); } ``` 4. If HTML rendering is unavoidable, sanitize every externally influenced field with a maintained allowlist-based sanitizer such as DOMPurify. Encoding must be appropriate to the destination context; HTML escaping alone is not sufficient for style, URL, or attribute contexts. 5. Validate the API response schema before rendering: - Require finite numeric values for scores and financial fields. - Restrict verdicts to `SNIPE`, `CAUTION`, or `AVOID`. - Enforce reasonable string lengths. - Treat all token names, symbols, URLs, and signals as untrusted text. 6. Add a restrictive Content Security Policy, for example by disallowing inline scripts and event handlers. Refactor the current inline script into a separate static file before enforcing such a policy. 7. Add automated tests using token metadata containing tags, event handlers, malformed markup, and encoded payloads. ]]>
