You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Deploy page 'Copy error' button re-parses error text as HTML — strips <…> from copied stack traces and creates an injection sink #595
The 📋 Copy error button on the deploy page's failure banner parses the error text as HTML before copying it. That has two effects:
Wrong clipboard content. Anything tag-like in the error is stripped. Stack traces lose frames such as at foo (<anonymous>), and any error that mentions <something> is copied incomplete.
An HTML-injection sink. HTML inside the error message is parsed, and runs, when the user clicks Copy. Example: <img src=x onerror=alert(1)>. Browsers load <img> elements created in a detached div, so onerror fires even though nothing is added to the page.
This landed in #464 (merged 2026-08-02) as the "fix" for a Graphite review comment (discussion_r3635422314). That comment was a false positive: the original code had no bug to fix.
Where (main @ a66e7b5790)
src/deploy/index.js — buildDeployErrorHtml, around lines 313–320
deploy/index.html — the inlined copy of buildDeployErrorHtml, around lines 1998–2007
src/deploy/tests/index.test.js — lines 717–725 assert the buggy handler (expect(html).toContain('d.innerHTML=this.dataset.copy'))
showStatus() inserts this markup with msg.innerHTML = html. The browser's HTML parser decodes the entity-escaped data-copy attribute, so this.dataset.copy already holds the original, unescaped error text. The d.innerHTML = … step then parses that raw text as HTML a second time. The escaping escapeHtml provides (it escapes & < > " ') is right for placing text inside the attribute; it just must not be undone and re-parsed.
Keep data-copy="${safeErrorDetail}", since escaping is correct for the attribute.
Acceptance criteria
Both src/deploy/index.js and deploy/index.html use the direct navigator.clipboard.writeText(this.dataset.copy) handler. Neither contains d.innerHTML=this.dataset.copy.
Replace the test at src/deploy/tests/index.test.js:717–725 with one that asserts the copy handler does not re-parse through innerHTML, and that data-copy still holds the HTML-escaped value.
Add a DOM-level test (jsdom): render buildDeployErrorHtml for an error whose stack is Error: bad <tag> at foo (<anonymous>) <img src=x onerror="window.__pwned=1">, insert it with innerHTML, stub navigator.clipboard.writeText, and click the button. The stub receives the exact original string, and window.__pwned stays undefined.
The existing deploy UI tests pass.
Severity
Low. The error text comes from Apps Script / Google API failures during the user's own deploy, so an attacker would need to control an error message. The clipboard corruption, though, affects every user who copies a stack trace, and the sink should not exist.
Found in a quality audit of Graphite AI review comments (2026-09-22). The review comment was wrong, and the automated fix was accepted without checking it.
Activity