Repository navigation
fix(deploy): copy error text verbatim, never re-parse it as HTML - #643
Conversation
The Copy error button set a detached div's innerHTML to data-copy and copied its textContent. The browser has already decoded the escaped attribute, so this parsed the raw error text as HTML a second time: it stripped <...> from copied stack traces and let markup in an error run (e.g. <img onerror>). Copy this.dataset.copy directly in both src/deploy/index.js and the inlined copy in deploy/index.html. Replace the test that asserted the innerHTML handler, guard the inlined copy against regressing, and add a Playwright test that clicks Copy on a stack trace containing <script>, <T> and <img onerror> and checks the clipboard gets the exact string and nothing executes. Closes #595 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This comment has been minimized.
This comment has been minimized.
Dev-Lead — waiting on PR blockers (intent: review-changes)PR: #643 |
|
Note @don-petry I reviewed this PR and no code changes were needed, but I can't mark it done yet: required checks |
QA Lead — test-risk advisoryRisk tier: LOW. This PR shrinks risk: it removes an HTML re-parse that let markup in error text run. It is guarded at two levels: a Jest check on the HTML string, and a Playwright test in real Chromium that includes a control and is reported to fail on What I'd shore up (highest leverage first):
Escalate? no. The defect is fixed and has regression coverage; these are hardening items. Advisory only — I comment, I do not change code. Ground: BMAD Test Architecture |
|
Advisory bots were rate-limited; auto-approval is withheld until they recover. pr-review-sweep will re-review this PR after 2026-10-21T23:42:01Z. |
|
No description provided. |
Dev-Lead — review-changes (no-changes)No changes were needed for this PR. |
|
No description provided. |
|
No actionable findings — the bot's review failed to complete due to a transient issue, and no specific code defects were reported. All CI checks (Node.js Tests, SonarCloud, CodeQL, Playwright, etc.) have passed. No code changes are needed. |
Dev-Lead — fix-bot-comment (no-changes)Agent reasoning |
|
Advisory bots were rate-limited; auto-approval is withheld until they recover. pr-review-sweep will re-review this PR after 2026-10-22T01:23:16Z. |
|
No description provided. |
This comment has been minimized.
This comment has been minimized.
Dev-Lead — fix-bot-comment (no-changes)Agent reasoning |
|
No description provided. |
|
|
Advisory bots were rate-limited; auto-approval is withheld until they recover. pr-review-sweep will re-review this PR after 2026-10-22T06:15:58Z. |
This comment has been minimized.
This comment has been minimized.
Dev-Lead — fix-reviews (no-changes)Agent reasoning |
|
No description provided. |
|
CodeRabbit's automated review and pre-merge checks all passed — no actionable findings or specific code issues identified. The comment summarizes the deployed error-copy fix across both the source handler and the shipped UI, with all validation passing. |
Dev-Lead — fix-bot-comment (no-changes)Agent reasoning |
donpetry-bot
left a comment
There was a problem hiding this comment.
Automated review — APPROVED ✓
Risk: LOW
Reviewed commit: d5a91756ad42e6f11fedf9d554c5048c189f2743
Review mode: triage-approved (single reviewer)
Summary
Removes the HTML re-parse in the deploy page's Copy error button. The handler now writes this.dataset.copy straight to the clipboard, in both src/deploy/index.js and its hand-kept mirror deploy/index.html. Jest and Playwright regression tests cover it. Small, contained change that removes an injection sink. Triage's low-risk call is confirmed.
Linked issue analysis
Closes #595. All four acceptance criteria are met:
- Both files now use
navigator.clipboard.writeText(this.dataset.copy), and neither containsd.innerHTML=this.dataset.copy. - The old test that asserted the buggy handler is replaced. The new test asserts the direct handler, no
innerHTML/textContent, and thatdata-copystill holds<script>. A second Jest test readsdeploy/index.htmlso the mirror can't silently regress. - The DOM-level test is done in Playwright (real Chromium, against the shipped page) instead of jsdom; the PR explains why (no jsdom environment in the repo). It checks that the exact original string reaches the stubbed clipboard and that
window.__pwnedstays undefined, with a control<img onerror>showing the payload would fire. - Existing tests pass in CI (Node.js Tests, Playwright UI Tests, build-and-test, coverage).
Findings
No blocking findings.
- Correctness:
escapeHtmlescapes& < > " ', sodata-copyis safe as an attribute. The parser decodes it once, sodataset.copyis the original text. Dropping the second parse fixes both the stripped stack-trace content and the onerror sink. - The
//comments sit between+-joined template literals, which is valid JS and doesn't change the output string. - Non-blocking hardening (already raised in the QA-lead advisory): an explicit
"assertion in the unit test, a parity test between the two mirrored copies ofbuildDeployErrorHtml, and an extra tick after the control fires in the XSS check to rule out ordering flakiness. - No unresolved review threads and no human change requests. Codex's review failed for operational reasons with no findings, and CodeRabbit had no actionable comments.
- MCP secret scan:
run_secret_scanningis not available in this run. The gitleaks check passed.
CI status
All required checks are green: build-and-test, coverage, Node.js Tests, Playwright UI Tests, CodeQL (actions, javascript-typescript, python), SonarCloud (quality gate passed), Secret scan (gitleaks), npm audit, and AgentShield. The rest were skipped because they don't apply.
Reviewed automatically by the PR-review agent (single-reviewer mode: opus 5.5 [opus 4.8, opus 4.7]). Reply if you need a human review.
|
pr-review approved on PARTIAL advisory evidence: 1/5 required advisory bots reported before the gate's head-age-timeout fallback proceeded. Recorded for the miss-rate metric (#1596). |



Closes #595
What
The deploy page's 📋 Copy error button set a detached
div'sinnerHTMLtothis.dataset.copyand copied itstextContent. The browser has already decoded the entity-escapeddata-copyattribute, so this parsed the raw error text as HTML a second time:<anonymous>,List<T>, …)<img src=x onerror=…>still loads and fires)The handler is now
navigator.clipboard.writeText(this.dataset.copy).data-copystays HTML-escaped, which is correct for an attribute.deploy/index.htmlis a hand-kept mirror ofsrc/deploy/index.js, not generated from it, so both copies change.Tests
d.innerHTML=this.dataset.copydeploy/index.htmland fails if the inlined copy regressessrc/deploy/tests/index.test.js: asserts the direct handler, noinnerHTML/textContent, and thatdata-copystill holds<script>window.__pwnedstays undefineddeploy/tests/ui.spec.js(Playwright, real Chromium, against the shippeddeploy/index.html). Renders a stack with<tag>,<anonymous>,List<T>,<script>and<img onerror>, clicks Copy with a stubbed clipboard, and checks the exact string. A control<img onerror>in a detached div proves the payload would fire in this browser, then asserts__pwnedis still undefined. Fails againstmain.Playwright stands in for the jsdom test the issue suggests: the repo has no
jest-environment-jsdom, and Playwright exercises the inlined page that actually ships.Local checks:
npm run check(prettier + eslint: 0 errors, 22 pre-existing warnings outside these files),npm run typecheck,npm test -- --coverage,npx playwright test.No
.github/workflows/changes (#480).🤖 Generated with Claude Code
Problem
See the linked issue.
Risk
Low — 4 file(s) changed; no CI- or runtime-critical paths touched.
Test plan
Tests added/updated:
deploy/tests/ui.spec.js,src/deploy/tests/index.test.js. Verification:bash scripts/dev-lead-lint.sh(shellcheck --severity=warning) ran pre-commit; the bats suite runs in CI.Rollback
Revert this PR. No non-revertible side effects (no tags, migrations, or external state).
Monitoring
This PR's CI checks (Lint + bats) are the success/regression signal.
Summary by CodeRabbit
Bug Fixes
Tests