T09 · Insecure Skill Coding Practices
Warning
- Location
- html-mark.js:697
- Finding
- Persistent Plaintext Storage of Annotated DOM Content and Input Values<![CDATA[ ## Vulnerability Details **File Location**: `html-mark.js:697-705`, with sensitive data collected at `html-mark.js:747-758` **Vulnerability Type**: Persistent client-side exposure of potentially sensitive page content **Risk Level**: Medium The same implementation is duplicated in `docs/html-mark.js` at the corresponding line ranges. ### Vulnerable Code Sensitive element content, including the current value of an annotated input, is collected and retained: ```javascript if (cur.matches('input, select, textarea')) { const v = cur.placeholder || cur.value || ''; return { label: cur.tagName.toLowerCase(), selector: cur.tagName.toLowerCase(), text: v.slice(0, 80), target: cur }; } ``` An HTML snapshot of the selected element is also captured: ```javascript const ann = { id: id, ctx: getContext(), label: desc.label, selector: desc.selector, text: desc.text, path: cssPath(desc.target), html: desc.target && desc.target.outerHTML ? desc.target.outerHTML.replace(/\s+/g, ' ').slice(0, 200) : '', note: '', pinEl: null, targetEl: desc.target, pageX: e.pageX, pageY: e.pageY }; ``` The collected content is then stored persistently and without protection in `localStorage`: ```javascript function save() { try { localStorage.setItem(STORE_KEY, JSON.stringify(annotations.map(function (a) { return { id: a.id, note: a.note, label: a.label, selector: a.selector, path: a.path, text: a.text, html: a.html, relX: a.relX, relY: a.relY, pageX: a.pageX, pageY: a.pageY }; }))); } catch (e) { /* storage unavailable or full — annotations stay in-memory */ } } ``` ### Technical Analysis The runtime is intended for injection into arbitrary pages, including live and third-party sites through a bookmarklet. When a user annotates an input, select, or textarea, `describeElement()` may copy up to 80 characters from the element's current value. The annotation also includes up to 200 character ...[truncated 3053 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Exclude sensitive form controls by default.** Never collect values from password fields or controls likely to contain credentials or personal information: ```javascript function safeControlText(el) { if (!el || !el.matches('input, select, textarea')) return ''; const type = (el.getAttribute('type') || '').toLowerCase(); const sensitiveTypes = ['password', 'hidden']; const sensitiveAutocomplete = [ 'current-password', 'new-password', 'one-time-code', 'cc-number', 'cc-csc' ]; if (sensitiveTypes.includes(type)) return ''; if (sensitiveAutocomplete.includes(el.autocomplete)) return ''; return el.placeholder || ''; } ``` Prefer storing a placeholder or semantic label rather than the current value. 2. **Do not persist raw `outerHTML`.** Generate a minimized structural description that removes values and sensitive attributes. At minimum, strip attributes such as `value`, `srcdoc`, inline event handlers, authorization data, tokens, and application-specific secret fields. 3. **Make persistence opt-in.** Keep annotations in memory by default and provide a clearly labeled “Remember annotations on this site” setting before writing to localStorage. 4. **Warn users before enabling persistence on live pages.** Explain that selected page text and review notes may be stored under the target site's origin and remain readable by scripts on that origin. 5. **Add expiration and explicit cleanup.** Store a creation timestamp and delete records after a short configurable retention period. Provide a control that removes every `html-mark:*` key for the current origin, not only the active pathname. 6. **Minimize persisted fields.** Store only the note, selector, and normalized position if persistence is necessary. Generate HTML snapshots only during an explicit export operation and keep them in memory. 7. **Apply the fix to both runtime copies.** Keep `html-mark.js` and `docs/html-mark.js` synchronized ...[truncated 315 chars]
