T09 · Insecure Skill Coding Practices
- Location
compliance-guardian.html:398- Finding
Persistent DOM-Based Cross-Site Scripting in Filing Tracker
- Content
View full analysis
Vulnerability Details
File Location:
compliance-guardian.html, lines 398–405 and 412–434
Vulnerability Type: Persistent DOM-based cross-site scripting (XSS)
Risk Level: HighVulnerable Code
The application stores a user-controlled property address in browser storage without validation or encoding:
javascript let filings = JSON.parse(localStorage.getItem('fincen-filings') || '[]'); filings.push({ address: address, closing: closing, deadline: deadline.toISOString(), added: new Date().toISOString() }); localStorage.setItem('fincen-filings', JSON.stringify(filings));It later interpolates that value directly into an HTML template and assigns the result to
innerHTML:javascript const filings = JSON.parse(localStorage.getItem('fincen-filings') || '[]'); const list = document.getElementById('tracker-list'); if (filings.length === 0) { list.innerHTML = '<p style="color:#888899;font-size:13px;text-align:center;">No pending filings. Add transactions from the Pre-Filler.</p>'; return; } const today = new Date(); list.innerHTML = filings.map((f, i) => { const deadline = new Date(f.deadline); const days = Math.ceil((deadline - today) / (1000 * 60 * 60 * 24)); let statusClass = 'status-green'; let statusText = `${days} days`; if (days <= 7) { statusClass = 'status-red'; statusText = days <= 0 ? 'OVERDUE' : `${days} days`; } else if (days <= 14) { statusClass = 'status-yellow'; } return ` <div class="tracker-item"> <div> <div class="address">${f.address}</div> <div class="dates">Closed: ${new Date(f.closing).toLocaleDateString()} • Due: ${deadline.toLocaleDateString()}</div> </div> <span class="status ${statusClass}">${statusText}</span> </div>`; }).join('');Technical Analysis
The property address originates from the
prop-addressform field and is tre ...[truncated 2106 chars]- Remediation
View remediation
Remediation Suggestions
- Do not construct tracker entries with HTML strings containing untrusted data. Create DOM elements and assign property addresses through
textContent. - If HTML rendering is unavoidable, process every untrusted value with a well-maintained contextual HTML sanitizer before insertion. Simple character replacement should not be treated as a general-purpose sanitizer.
- Validate data read from
localStorageagainst an expected schema. Require strings of reasonable lengths and valid date formats, and reject malformed records. - Treat browser storage as attacker-modifiable rather than trusted merely because it is local.
- Add a restrictive Content Security Policy that disallows inline scripts and inline event handlers, preferably using external scripts with hashes or nonces. This should be defense in depth rather than the primary fix.
- Clear or migrate existing
fincen-filingsrecords after deploying the correction so previously stored payloads cannot remain active. - Add regression tests using addresses containing HTML metacharacters and event-handler markup, verifying that the value is displayed only as literal text.
A safe rendering pattern is:
javascript const item = document.createElement('div'); item.className = 'tracker-item'; const details = document.createElement('div'); const addressElement = document.createElement('div'); addressElement.className = 'address'; addressElement.textContent = f.address; const datesElement = document.createElement('div'); datesElement.className = 'dates'; datesElement.textContent = `Closed: ${new Date(f.closing).toLocaleDateString()} • Due: ${deadline.toLocaleDateString()}`; const statusElement = document.createElement('span'); statusElement.className = `status ${statusClass}`; statusElement.textContent = statusText; details.append(addressElement, datesElement); item.append(details, statusElement); list.appendChild(item);- Do not construct tracker entries with HTML strings containing untrusted data. Create DOM elements and assign property addresses through
