T09 · Insecure Skill Coding Practices
Error
- Location
- template-dashboard/public/app.js:114
- Finding
- Stored Cross-Site Scripting Through Unsanitized Fulcra Record Values<![CDATA[ ## Vulnerability Details **File Location**: `template-dashboard/public/app.js:114-132`; `template-dashboard/public/index.html:76` **Vulnerability Type**: Stored cross-site scripting caused by rendering untrusted record data as HTML **Risk Level**: High ### Vulnerable Code ```javascript formatValue(d) { if (d.value === undefined || d.value === null) return ''; // Special formatting for ScaleAnnotations if (d.metadata && d.metadata.measurement_spec && d.metadata.measurement_spec.measurement_type === 'scale') { const maxVal = d.metadata.measurement_spec.scale.max_allowed || 5; let textStr = `${d.value}/${maxVal}`; // Look for a custom label mapping if (d.metadata.spec && d.metadata.spec.scale && d.metadata.spec.scale.label_mapping && d.metadata.spec.scale.label_mapping.string && d.metadata.spec.scale.label_mapping.string.mapping) { const label = d.metadata.spec.scale.label_mapping.string.mapping[String(d.value)]; if (label) { textStr += ` — <em>${label}</em>`; } } return textStr; } // Fallback for primitive values return d.value; } ``` The returned value is rendered through an HTML interpretation sink: ```html <span class="entry-value" x-html="formatValue(detail.item)"></span> ``` ### Technical Analysis Timeline records are read from JSONL files and passed to `formatValue()`. Both `d.value` and the metadata-derived `label` can originate from downloaded Fulcra records. The function concatenates these values into an HTML string without escaping or sanitization. The dashboard then uses Alpine.js `x-html`, which assigns interpreted HTML rather than text. Consequently, a malicious record value such as an element with an event handler can introduce executable browser content. The fallback path is also vulnerable because it returns `d.value` directly to the same HTML sink. This is a stored XSS condition because ...[truncated 1485 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Replace the HTML binding with a text binding: ```html <span class="entry-value" x-text="formatValue(detail.item)"></span> ``` 2. Do not use HTML entities or tags in `formatValue()`. Return plain text instead: ```javascript formatValue(d) { if (d.value === undefined || d.value === null) return ''; if ( d.metadata?.measurement_spec?.measurement_type === 'scale' ) { const maxVal = d.metadata.measurement_spec.scale?.max_allowed || 5; let result = `${String(d.value)}/${String(maxVal)}`; const mapping = d.metadata?.spec?.scale?.label_mapping?.string?.mapping; const label = mapping?.[String(d.value)]; if (label) { result += ` — ${String(label)}`; } return result; } return String(d.value); } ``` 3. If emphasized formatting is required, render the value and label in separate elements, each using `x-text`, instead of assembling HTML. 4. If arbitrary HTML is an unavoidable product requirement, sanitize it with a pinned, audited sanitizer and a strict element-and-attribute allowlist before passing it to `x-html`. 5. Add a restrictive Content Security Policy as defense in depth. Avoid permitting inline scripts or event handlers. 6. Add tests using payloads containing elements, event handlers, malformed tags, SVG content, and encoded markup to verify that all record fields are rendered only as text. ]]>
