Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions src/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ function createNode(text: string): Element {
return node
}

export function copyNode(node: Element): Promise<void> {
if ('clipboard' in navigator) {
return navigator.clipboard.writeText(node.textContent || '')
}
// Some characters, such as the non-breaking space (U+00A0), render
// identically to a regular space but are copied to the clipboard as their
// original, non-printable code point. This can be abused to hide malicious
// content (for example in shell commands) that is invisible in the rendered
// page. Normalizing these characters ensures the copied text matches what the
// user sees. See https://hackerone.com/reports/1805414
function normalizeText(text: string): string {
return text.replace(/\u00A0/g, ' ')
}

function copySelection(node: Element): Promise<void> {
const selection = getSelection()
if (selection == null) {
return Promise.reject(new Error())
Expand All @@ -29,19 +35,25 @@ export function copyNode(node: Element): Promise<void> {
return Promise.resolve()
}

export function copyNode(node: Element): Promise<void> {
return copyText(node.textContent || '')
}

export function copyText(text: string): Promise<void> {
const normalized = normalizeText(text)

if ('clipboard' in navigator) {
return navigator.clipboard.writeText(text)
return navigator.clipboard.writeText(normalized)
}

const body = document.body
if (!body) {
return Promise.reject(new Error())
}

const node = createNode(text)
const node = createNode(normalized)
body.appendChild(node)
copyNode(node)
copySelection(node)
body.removeChild(node)
return Promise.resolve()
Comment on lines +54 to 58
}
25 changes: 25 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,31 @@ describe('clipboard-copy element', function () {
assert.equal(text, 'I am a link')
})

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')
})
Comment on lines +157 to +180

it('does not copy when disabled', async function () {
const target = document.createElement('div')
target.innerHTML = 'Hello world!'
Expand Down
Loading