T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/dashboard.html:520
- Finding
- Stored Cross-Site Scripting in Transaction Dashboard<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dashboard.html:520-539`, with unsafe input accepted at `scripts/main.py:65-68` and `scripts/parser.py:258-266` **Vulnerability Type**: Stored Cross-Site Scripting **Risk Level**: High ### Vulnerable Code ```javascript function renderTable() { const tb = qs('tbody'); tb.innerHTML = ''; const rows = [...state.tableRows]; const mult = state.sortDir === 'asc' ? 1 : -1; rows.sort((a, b) => { const ka = state.sortKey; let va = a[ka], vb = b[ka]; if (ka === 'amount') { va = Number(va); vb = Number(vb); return mult * (va - vb); } return mult * String(va || '').localeCompare(String(vb || ''), 'zh-CN'); }); rows.forEach((r) => { const tr = document.createElement('tr'); const tag = r.type === '收入' ? 'tag-in' : 'tag-out'; tr.innerHTML = `<td>${r.date}</td><td><span class="tag ${tag}">${r.type}</span></td><td>${r.category}</td>` + `<td class="num">${fmtMoney(r.amount)}</td><td>${(r.note || '').replace(/</g, '<')}</td>`; tb.appendChild(tr); }); } ``` The corresponding CLI accepts database-controlled values without validating the date format or constraining the category: ```python date_str = str(payload["date"]) type_ = str(payload["type"]) category = str(payload["category"]) amount = float(payload["amount"]) ``` ```python if type_ not in ("收入", "支出"): issues.append("type must be income or expense") if type_ == "支出" and category not in expense_defaults: pass # custom categories allowed if type_ == "收入" and category not in income_defaults: pass ``` ### Technical Analysis The dashboard constructs table rows through `innerHTML` and directly interpolates the transaction's `date` and `category` fields. Both fields originate from SQLite records and can contain arbitrary HTML because the record-validation function does not validate dates or restrict custom-category content. A payload such as the following can therefore be s ...[truncated 1943 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Stop constructing transaction rows through `innerHTML`. 2. Create each table cell with `document.createElement()` and assign all database values through `textContent`. 3. Validate `date` with a strict ISO `YYYY-MM-DD` parser before insertion and reject invalid or trailing content. 4. Apply reasonable length limits to category, note, source, and date fields. 5. If HTML rendering is ever necessary, use a maintained allowlist-based sanitizer rather than manual character replacement. 6. Add regression tests that store HTML and event-handler payloads in every transaction field and verify that the dashboard displays them as plain text. 7. Deploy a restrictive Content Security Policy, such as one that disallows inline scripts, as defense in depth. CSP must supplement rather than replace safe DOM construction. A safe rendering pattern is: ```javascript function appendTextCell(row, value, className = '') { const cell = document.createElement('td'); if (className) cell.className = className; cell.textContent = value == null ? '' : String(value); row.appendChild(cell); } ``` ]]>
