Normalize non-breaking spaces when copying to clipboard#87
Merged
Conversation
Copilot
AI
changed the title
[WIP] Fix web interface copy button vulnerability
Normalize non-breaking spaces when copying to clipboard
Jun 15, 2026
|
@liuliu-dev this probably shouldn't be in a public repo. Looks like the entire bounty report got included in this. |
jonrohan
approved these changes
Jun 15, 2026
jonrohan
approved these changes
Jun 15, 2026
There was a problem hiding this comment.
Pull request overview
This PR mitigates a clipboard spoofing risk by normalizing non‑breaking spaces (U+00A0) to regular ASCII spaces so copied text matches what users see, covering both modern navigator.clipboard and the legacy execCommand fallback paths.
Changes:
- Added
normalizeText()and applied it to all copy paths. - Refactored legacy selection/
execCommandlogic intocopySelection()and routedcopyNode()throughcopyText(). - Added tests asserting U+00A0 normalization for both
for-target content and thevalueattribute path.
Show a summary per file
| File | Description |
|---|---|
src/clipboard.ts |
Adds normalization and refactors copy flow to ensure normalization is applied across clipboard and fallback paths. |
test/test.js |
Adds normalization assertions for element-content and value-attribute copy paths. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 2/2 changed files
- Comments generated: 3
Comment on lines
33
to
35
| document.execCommand('copy') | ||
| selection.removeAllRanges() | ||
| return Promise.resolve() |
Comment on lines
+54
to
58
| const node = createNode(normalized) | ||
| body.appendChild(node) | ||
| copyNode(node) | ||
| copySelection(node) | ||
| body.removeChild(node) | ||
| return Promise.resolve() |
Comment on lines
+157
to
+180
| it('normalizes non-breaking spaces to regular spaces', async function () { | ||
| const target = document.createElement('div') | ||
| target.textContent = 'wget -O - https://example.com/hello.sh\u00A0| bash' | ||
| target.id = 'copy-target' | ||
| document.body.append(target) | ||
|
|
||
| const button = document.querySelector('clipboard-copy') | ||
| button.click() | ||
|
|
||
| const text = await whenCopied | ||
| assert.equal(text, 'wget -O - https://example.com/hello.sh | bash') | ||
| assert.notInclude(text, '\u00A0') | ||
| }) | ||
|
|
||
| it('normalizes non-breaking spaces from the value attribute', async function () { | ||
| const button = document.querySelector('clipboard-copy') | ||
| button.setAttribute('value', 'hello.sh\u00A0| bash') | ||
|
|
||
| button.click() | ||
|
|
||
| const text = await whenCopied | ||
| assert.equal(text, 'hello.sh | bash') | ||
| assert.notInclude(text, '\u00A0') | ||
| }) |
Member
|
@maclarel Thanks for the heads up, sorry about the ping 🙇🏻 How have you been? |
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.
Closes https://github.com/github/primer/issues/1962
Non-printable characters like the non-breaking space (U+00A0) render identically to a regular space but are copied verbatim to the clipboard. This lets an attacker hide command fragments inside otherwise innocuous-looking text (e.g. a markdown code block or blob), so that pasting a copied command into a terminal executes something the victim never saw (HackerOne #1805414).
Changes
src/clipboard.tsnormalizeText()to replace U+00A0 with a regular ASCII space so copied text matches what is rendered.copyText()normalizes before writing, andcopyNode()now delegates tocopyText().execCommandselection logic intocopySelection(), operating on a normalized node so the non-navigator.clipboardfallback is covered too.test/test.js— added cases asserting normalization for both element-content (for) andvalue-attribute copy paths.