mirror of
https://github.com/kovidgoyal/kitty.git
synced 2024-09-21 11:39:57 +03:00
Always ask for confirmation when pasting text with control codes in it
This commit is contained in:
parent
56963c693e
commit
defa2e29ac
@ -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`
|
||||
|
||||
- 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]
|
||||
|
@ -523,7 +523,7 @@
|
||||
'''
|
||||
)
|
||||
|
||||
opt('paste_actions', 'quote-urls-at-prompt',
|
||||
opt('paste_actions', 'quote-urls-at-prompt,confirm',
|
||||
option_type='paste_actions',
|
||||
long_text='''
|
||||
A comma separated list of actions to take when pasting text into the terminal.
|
||||
@ -533,8 +533,12 @@
|
||||
If the text being pasted is a URL and the cursor is at a shell prompt,
|
||||
automatically quote the URL (needs :opt:`shell_integration`).
|
||||
:code:`confirm`:
|
||||
Confirm the paste if bracketed paste mode is not active or there is
|
||||
a large amount of text being pasted.
|
||||
Confirm the paste if the text to be pasted contains any terminal control codes
|
||||
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`:
|
||||
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
|
||||
|
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)
|
||||
mouse_hide_wait: float = 0.0 if is_macos else 3.0
|
||||
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'
|
||||
pointer_shape_when_dragging: choices_for_pointer_shape_when_dragging = 'beam'
|
||||
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]:
|
||||
s = frozenset({'quote-urls-at-prompt', 'confirm', 'filter'})
|
||||
s = frozenset({'quote-urls-at-prompt', 'confirm', 'filter', 'confirm-if-large'})
|
||||
q = frozenset(x.lower().split(','))
|
||||
if not q.issubset(s):
|
||||
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
|
||||
|
||||
|
||||
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~')
|
||||
while True:
|
||||
new_text = pat.sub(b'', text)
|
||||
if new_text == text:
|
||||
break
|
||||
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
|
||||
|
||||
|
||||
|
@ -1468,18 +1468,31 @@ def paste_with_actions(self, text: str) -> None:
|
||||
text = shlex.quote(text)
|
||||
btext = text.encode('utf-8')
|
||||
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 = ''
|
||||
limit = 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:
|
||||
if len(btext) > 16 * 1024:
|
||||
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_paste_confirmation, btext), window=self)
|
||||
get_boss().confirm(msg + _(' Are you sure?'), partial(self.handle_large_paste_confirmation, btext), window=self)
|
||||
return
|
||||
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:
|
||||
self.paste_text(btext)
|
||||
|
||||
@ -1494,7 +1507,7 @@ def paste_text(self, text: Union[str, bytes]) -> None:
|
||||
if isinstance(text, str):
|
||||
text = text.encode('utf-8')
|
||||
if self.screen.in_bracketed_paste_mode:
|
||||
text = sanitize_for_bracketed_paste(text, self.at_prompt)
|
||||
text = sanitize_for_bracketed_paste(text)
|
||||
else:
|
||||
# Workaround for broken editors like nano that cannot handle
|
||||
# newlines in pasted text see https://github.com/kovidgoyal/kitty/issues/994
|
||||
|
@ -590,7 +590,6 @@ def test_bracketed_paste_sanitizer(self):
|
||||
self.assertNotIn(b'\x1b[201~', q)
|
||||
self.assertNotIn('\x9b201~'.encode('utf-8'), 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):
|
||||
for src, expected in {
|
||||
|
Loading…
Reference in New Issue
Block a user