diff --git a/src/prompt_toolkit/history.py b/src/prompt_toolkit/history.py index 662e10460..fbbbec222 100644 --- a/src/prompt_toolkit/history.py +++ b/src/prompt_toolkit/history.py @@ -296,8 +296,14 @@ def add() -> None: return reversed(strings) def store_string(self, string: str) -> None: - # Save to file. - with open(self.filename, "ab") as f: + # Save to file. The history can contain anything the user typed, so + # create the file readable by the owner only (like readline does). + fd = os.open( + self.filename, + os.O_WRONLY | os.O_APPEND | os.O_CREAT | getattr(os, "O_BINARY", 0), + 0o600, + ) + with open(fd, "ab") as f: def write(t: str) -> None: f.write(t.encode("utf-8", errors="replace")) diff --git a/tests/test_history.py b/tests/test_history.py index 500b7f119..3820945a2 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -1,7 +1,12 @@ from __future__ import annotations +import os +import stat +import sys from asyncio import run +import pytest + from prompt_toolkit.history import FileHistory, InMemoryHistory, ThreadedHistory @@ -60,6 +65,18 @@ def test_file_history(tmpdir): assert _call_history_load(history2) == ["test3", "world", "hello"] +@pytest.mark.skipif(sys.platform == "win32", reason="POSIX file modes") +def test_file_history_permissions(tmpdir): + histfile = tmpdir.join("history") + old_umask = os.umask(0o022) + try: + FileHistory(histfile).append_string("hello") + finally: + os.umask(old_umask) + + assert stat.S_IMODE(os.stat(histfile).st_mode) == 0o600 + + def test_threaded_file_history(tmpdir): histfile = tmpdir.join("history")