mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-19 15:04:50 +02:00
Always ask for confirmation when pasting text with control codes in it
This commit is contained in:
@@ -72,7 +72,7 @@ Detailed list of changes
|
|||||||
|
|
||||||
- Two new event types for :ref:`watchers <watchers>`, :code:`on_title_change` and :code:`on_set_user_var`
|
- Two new event types for :ref:`watchers <watchers>`, :code:`on_title_change` and :code:`on_set_user_var`
|
||||||
|
|
||||||
- When pasting in bracketed paste mode and the cursor is at a shell prompt, strip out C0 control codes as some shells incorrectly interpret these allowing escape from bracketed paste mode. Thanks to David Leadbetter for discovering.
|
- When pasting, if the text contains terminal control codes ask the user for permission. See :opt:`paste_actions` for details. Thanks to David Leadbetter for discovering this.
|
||||||
|
|
||||||
|
|
||||||
0.30.1 [2023-10-05]
|
0.30.1 [2023-10-05]
|
||||||
|
|||||||
@@ -523,7 +523,7 @@ clipboard.
|
|||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
|
|
||||||
opt('paste_actions', 'quote-urls-at-prompt',
|
opt('paste_actions', 'quote-urls-at-prompt,confirm',
|
||||||
option_type='paste_actions',
|
option_type='paste_actions',
|
||||||
long_text='''
|
long_text='''
|
||||||
A comma separated list of actions to take when pasting text into the terminal.
|
A comma separated list of actions to take when pasting text into the terminal.
|
||||||
@@ -533,8 +533,12 @@ The supported paste actions are:
|
|||||||
If the text being pasted is a URL and the cursor is at a shell prompt,
|
If the text being pasted is a URL and the cursor is at a shell prompt,
|
||||||
automatically quote the URL (needs :opt:`shell_integration`).
|
automatically quote the URL (needs :opt:`shell_integration`).
|
||||||
:code:`confirm`:
|
:code:`confirm`:
|
||||||
Confirm the paste if bracketed paste mode is not active or there is
|
Confirm the paste if the text to be pasted contains any terminal control codes
|
||||||
a large amount of text being pasted.
|
as this can be dangerous, leading to code execution if the shell/program running
|
||||||
|
in the terminal does not properly handle these.
|
||||||
|
:code:`confirm-if-large`
|
||||||
|
Confirm the paste if it is very large (larger than 16KB) as pasting
|
||||||
|
large amounts of text into shells can be very slow.
|
||||||
:code:`filter`:
|
:code:`filter`:
|
||||||
Run the filter_paste() function from the file :file:`paste-actions.py` in
|
Run the filter_paste() function from the file :file:`paste-actions.py` in
|
||||||
the kitty config directory on the pasted text. The text returned by the
|
the kitty config directory on the pasted text. The text returned by the
|
||||||
|
|||||||
2
kitty/options/types.py
generated
2
kitty/options/types.py
generated
@@ -544,7 +544,7 @@ class Options:
|
|||||||
mark3_foreground: Color = Color(0, 0, 0)
|
mark3_foreground: Color = Color(0, 0, 0)
|
||||||
mouse_hide_wait: float = 0.0 if is_macos else 3.0
|
mouse_hide_wait: float = 0.0 if is_macos else 3.0
|
||||||
open_url_with: typing.List[str] = ['default']
|
open_url_with: typing.List[str] = ['default']
|
||||||
paste_actions: typing.FrozenSet[str] = frozenset({'quote-urls-at-prompt'})
|
paste_actions: typing.FrozenSet[str] = frozenset({'confirm', 'quote-urls-at-prompt'})
|
||||||
placement_strategy: choices_for_placement_strategy = 'center'
|
placement_strategy: choices_for_placement_strategy = 'center'
|
||||||
pointer_shape_when_dragging: choices_for_pointer_shape_when_dragging = 'beam'
|
pointer_shape_when_dragging: choices_for_pointer_shape_when_dragging = 'beam'
|
||||||
pointer_shape_when_grabbed: choices_for_pointer_shape_when_grabbed = 'arrow'
|
pointer_shape_when_grabbed: choices_for_pointer_shape_when_grabbed = 'arrow'
|
||||||
|
|||||||
@@ -911,7 +911,7 @@ def shell_integration(x: str) -> FrozenSet[str]:
|
|||||||
|
|
||||||
|
|
||||||
def paste_actions(x: str) -> FrozenSet[str]:
|
def paste_actions(x: str) -> FrozenSet[str]:
|
||||||
s = frozenset({'quote-urls-at-prompt', 'confirm', 'filter'})
|
s = frozenset({'quote-urls-at-prompt', 'confirm', 'filter', 'confirm-if-large'})
|
||||||
q = frozenset(x.lower().split(','))
|
q = frozenset(x.lower().split(','))
|
||||||
if not q.issubset(s):
|
if not q.issubset(s):
|
||||||
log_error(f'Invalid paste actions: {q - s}, ignoring')
|
log_error(f'Invalid paste actions: {q - s}, ignoring')
|
||||||
|
|||||||
@@ -1135,16 +1135,13 @@ def docs_url(which: str = '', local_docs_root: Optional[str] = '') -> str:
|
|||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
def sanitize_for_bracketed_paste(text: bytes, at_shell_prompt: bool = False) -> bytes:
|
def sanitize_for_bracketed_paste(text: bytes) -> bytes:
|
||||||
pat = re.compile(b'(?:(?:\033\\\x5b)|(?:\x9b))201~')
|
pat = re.compile(b'(?:(?:\033\\\x5b)|(?:\x9b))201~')
|
||||||
while True:
|
while True:
|
||||||
new_text = pat.sub(b'', text)
|
new_text = pat.sub(b'', text)
|
||||||
if new_text == text:
|
if new_text == text:
|
||||||
break
|
break
|
||||||
text = new_text
|
text = new_text
|
||||||
if at_shell_prompt:
|
|
||||||
# some shells dont handle C0 control codes in pasted text correctly
|
|
||||||
text = re.sub(b'[\x00-\x1f]+', b'', text)
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1468,18 +1468,31 @@ class Window:
|
|||||||
text = shlex.quote(text)
|
text = shlex.quote(text)
|
||||||
btext = text.encode('utf-8')
|
btext = text.encode('utf-8')
|
||||||
if 'confirm' in opts.paste_actions:
|
if 'confirm' in opts.paste_actions:
|
||||||
|
sanitized = sanitize_control_codes(text)
|
||||||
|
if sanitized != text:
|
||||||
|
msg = _('The text to be pasted contains terminal control codes. If the terminal program you are pasting into does not properly'
|
||||||
|
' sanitize pasted text, this can lead to code execution vulnerabilities. How would you like to proceed?')
|
||||||
|
get_boss().choose(
|
||||||
|
msg, partial(self.handle_dangerous_paste_confirmation, btext, sanitized),
|
||||||
|
's;green:Sanitize and paste', 'p;red:Paste anyway', 'c;yellow:Cancel',
|
||||||
|
window=self, default='s',
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if 'confirm-if-large' in opts.paste_actions:
|
||||||
msg = ''
|
msg = ''
|
||||||
limit = 16 * 1024
|
if len(btext) > 16 * 1024:
|
||||||
if not self.screen.in_bracketed_paste_mode:
|
|
||||||
msg = _('Pasting text into shells that do not support bracketed paste can be dangerous.')
|
|
||||||
elif len(btext) > limit:
|
|
||||||
msg = _('Pasting very large amounts of text ({} bytes) can be slow.').format(len(btext))
|
msg = _('Pasting very large amounts of text ({} bytes) can be slow.').format(len(btext))
|
||||||
if msg:
|
get_boss().confirm(msg + _(' Are you sure?'), partial(self.handle_large_paste_confirmation, btext), window=self)
|
||||||
get_boss().confirm(msg + _(' Are you sure?'), partial(self.handle_paste_confirmation, btext), window=self)
|
|
||||||
return
|
return
|
||||||
self.paste_text(btext)
|
self.paste_text(btext)
|
||||||
|
|
||||||
def handle_paste_confirmation(self, btext: bytes, confirmed: bool) -> None:
|
def handle_dangerous_paste_confirmation(self, btext: bytes, sanitized: str, choice: str) -> None:
|
||||||
|
if choice == 's':
|
||||||
|
self.paste_text(sanitized)
|
||||||
|
elif choice == 'p':
|
||||||
|
self.paste_text(btext)
|
||||||
|
|
||||||
|
def handle_large_paste_confirmation(self, btext: bytes, confirmed: bool) -> None:
|
||||||
if confirmed:
|
if confirmed:
|
||||||
self.paste_text(btext)
|
self.paste_text(btext)
|
||||||
|
|
||||||
@@ -1494,7 +1507,7 @@ class Window:
|
|||||||
if isinstance(text, str):
|
if isinstance(text, str):
|
||||||
text = text.encode('utf-8')
|
text = text.encode('utf-8')
|
||||||
if self.screen.in_bracketed_paste_mode:
|
if self.screen.in_bracketed_paste_mode:
|
||||||
text = sanitize_for_bracketed_paste(text, self.at_prompt)
|
text = sanitize_for_bracketed_paste(text)
|
||||||
else:
|
else:
|
||||||
# Workaround for broken editors like nano that cannot handle
|
# Workaround for broken editors like nano that cannot handle
|
||||||
# newlines in pasted text see https://github.com/kovidgoyal/kitty/issues/994
|
# newlines in pasted text see https://github.com/kovidgoyal/kitty/issues/994
|
||||||
|
|||||||
@@ -590,7 +590,6 @@ class TestDataTypes(BaseTest):
|
|||||||
self.assertNotIn(b'\x1b[201~', q)
|
self.assertNotIn(b'\x1b[201~', q)
|
||||||
self.assertNotIn('\x9b201~'.encode('utf-8'), q)
|
self.assertNotIn('\x9b201~'.encode('utf-8'), q)
|
||||||
self.assertIn(b'ab', q)
|
self.assertIn(b'ab', q)
|
||||||
self.assertNotIn(b'\x03', sanitize_for_bracketed_paste(b'hi\x03world', True))
|
|
||||||
|
|
||||||
def test_expand_ansi_c_escapes(self):
|
def test_expand_ansi_c_escapes(self):
|
||||||
for src, expected in {
|
for src, expected in {
|
||||||
|
|||||||
Reference in New Issue
Block a user