Repository navigation
fix(dashboard): keep project names and session ids out of inline handlers - #641
Merged
Merged
Conversation
…lers The project row and the star badge built their onclick source with JSON.stringify(value).replace(/"/g, '"'). JSON.stringify does not escape `&`, and the browser decodes the attribute before compiling it as JS, so a literal `"` in the data became a real quote and escaped the string: a directory named `p");alert(1);("` ran code when its project row or star was clicked. Session ids are just as exposed, because metadata.session_id is accepted verbatim. #640 made the path easier to reach by recording cwd for live Claude traffic again. Both values now travel in data-* attributes (escapeHtml'd), and the handlers are fixed strings that read this.dataset, following the existing data-sid / data-resume convention. keyboard-nav.js read project names back by parsing the onclick source; it now reads data-project, which also fixes arrow-key navigation skipping a project whose name JSON.parse could not recover. An audit of every inline on*= handler under public/ found no other site that splices a data-controlled string: the rest use constants, numeric indexes, ccxray-generated entry ids, hex hashes, server-derived agent keys, or the data-* pattern already. There are no javascript: URLs. New puppeteer e2e (test/dashboard-xss-e2e.test.js) clicks the project row, the project star, the session star and the derived-star chip for a payload name and session id, and checks keyboard navigation. Old code: 5 of 6 fail; new code: all pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
摘要
修正 dashboard 的 DOM XSS。
onclick,而且只把"換成"。名稱裡如果本來就有字面的",瀏覽器解碼屬性後就會跳出 JS 字串、執行任意程式碼。fix(store): record cwd for live Claude Code 2.1.283 proxy traffic #640 讓即時流量開始記錄 cwd 之後,這條路徑更容易被觸發。data-*寫法,把這些值移出 inline handler。public/所有 inline handler,其他地方都安全。Details
JSON.stringify(value).replace(/"/g, '"')escaped quotes but not&. The browser HTML-decodes an attribute before running it as JS, so a literal"in the data became a real"and closed the JS string.53558f1) on both paths, using only a global counter as the payload effect:p");window.__ccxrayXss=…;("used as the request cwd/?importedmetadata.session_ideach ran the payload.data-sid/data-resumeconvention. Values go intodata-*attributes viaescapeHtml, and handlers become fixed code readingthis.dataset:data-projectrenderStarBadge's 6 handlers (project and session stars, derived-star counts):data-level/data-star-idkeyboard-nav.jsnow readsdata-projectinstead of parsing the row'sonclick. This also fixes names thatJSON.parsecould not recover, which keyboard navigation used to skip.public/(full table in the review artifacts):[0-9T-], 12-hex coreHashes, or server-derived agent keys restricted to[a-z0-9-].javascript:URLs,eval,new FunctionorsetAttribute('on…'). An independent enumeration (85 literal handlers, 12 property assignments) found nothing missed.test/dashboard-xss-e2e.test.js(puppeteer, same pattern asdashboard-codex-e2e). It covers the project row, project star, session star, derived-star count and its popover, and keyboard ↑/↓. Each asserts three things: the payload counter stays 0, selection and stars record the full original name (including/_api/stars), and state actually changes after the click. Every clicked element is asserted to exist first.Verification
CCXRAY_HOME=$(mktemp -d) CCXRAY_EXPORT_DISABLE=1 npm test: 2652/2652. The real~/.ccxraywas untouched during the run.test/dashboard-xss-e2e.test.jswith the pre-fixpublic/miller-columns.jsandpublic/keyboard-nav.jsfails 5 of 6; with the fix, 6/6 pass.--port 5602server (tempCCXRAY_HOMEandHOME), with headless Chrome via puppeteer (CDP):Not verified:
messages.jsdlScriptis currently safe only because agent keys are restricted to[a-z0-9-]. It puts anescapeHtml'd value inside a single-quoted JS string, which would become injectable if that restriction were relaxed. Queued as a separate hardening change.🤖 Generated with Claude Code