diff --git a/src/borg/helpers.py b/src/borg/helpers.py
index 15f56437..db66b822 100644
--- a/src/borg/helpers.py
+++ b/src/borg/helpers.py
@@ -916,7 +916,12 @@ def __str__(self):
     def to_key_filename(self):
         name = re.sub('[^\w]', '_', self.path).strip('_')
         if self.proto != 'file':
-            name = self.host + '__' + name
+            name = re.sub('[^\w]', '_', self.host) + '__' + name
+        if len(name) > 100:
+            # Limit file names to some reasonable length. Most file systems
+            # limit them to 255 [unit of choice]; due to variations in unicode
+            # handling we truncate to 100 *characters*.
+            name = name[:100]
         return os.path.join(get_keys_dir(), name)
 
     def __repr__(self):

Restrict file names generated by Location.to_keyfile_name
The generated file can currently include special characters like those mentioned in #2290, which should be stripped for compatibility reasons.

From #2555 
diff --git a/src/borg/testsuite/helpers.py b/src/borg/testsuite/helpers.py
index 7ce22dc2..b23e277b 100644
--- a/src/borg/testsuite/helpers.py
+++ b/src/borg/testsuite/helpers.py
@@ -50,10 +50,19 @@ def test_bin_to_hex():
 
 
 class TestLocationWithoutEnv:
-    def test_ssh(self, monkeypatch):
+    @pytest.fixture
+    def keys_dir(self, tmpdir, monkeypatch):
+        tmpdir = str(tmpdir)
+        monkeypatch.setenv('BORG_KEYS_DIR', tmpdir)
+        if not tmpdir.endswith(os.path.sep):
+            tmpdir += os.path.sep
+        return tmpdir
+
+    def test_ssh(self, monkeypatch, keys_dir):
         monkeypatch.delenv('BORG_REPO', raising=False)
         assert repr(Location('ssh://user@host:1234/some/path::archive')) == \
             "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive='archive')"
+        assert Location('ssh://user@host:1234/some/path::archive').to_key_filename() == keys_dir + 'host__some_path'
         assert repr(Location('ssh://user@host:1234/some/path')) == \
             "Location(proto='ssh', user='user', host='host', port=1234, path='/some/path', archive=None)"
         assert repr(Location('ssh://user@host/some/path')) == \
@@ -62,12 +71,14 @@ def test_ssh(self, monkeypatch):
             "Location(proto='ssh', user='user', host='::', port=1234, path='/some/path', archive='archive')"
         assert repr(Location('ssh://user@[::]:1234/some/path')) == \
             "Location(proto='ssh', user='user', host='::', port=1234, path='/some/path', archive=None)"
+        assert Location('ssh://user@[::]:1234/some/path').to_key_filename() == keys_dir + '____some_path'
         assert repr(Location('ssh://user@[::]/some/path')) == \
             "Location(proto='ssh', user='user', host='::', port=None, path='/some/path', archive=None)"
         assert repr(Location('ssh://user@[2001:db8::]:1234/some/path::archive')) == \
             "Location(proto='ssh', user='user', host='2001:db8::', port=1234, path='/some/path', archive='archive')"
         assert repr(Location('ssh://user@[2001:db8::]:1234/some/path')) == \
             "Location(proto='ssh', user='user', host='2001:db8::', port=1234, path='/some/path', archive=None)"
+        assert Location('ssh://user@[2001:db8::]:1234/some/path').to_key_filename() == keys_dir + '2001_db8____some_path'
         assert repr(Location('ssh://user@[2001:db8::]/some/path')) == \
             "Location(proto='ssh', user='user', host='2001:db8::', port=None, path='/some/path', archive=None)"
         assert repr(Location('ssh://user@[2001:db8::c0:ffee]:1234/some/path::archive')) == \
@@ -82,15 +93,17 @@ def test_ssh(self, monkeypatch):
             "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=1234, path='/some/path', archive=None)"
         assert repr(Location('ssh://user@[2001:db8::192.0.2.1]/some/path')) == \
             "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=None, path='/some/path', archive=None)"
+        assert Location('ssh://user@[2001:db8::192.0.2.1]/some/path').to_key_filename() == keys_dir + '2001_db8__192_0_2_1__some_path'
 
-    def test_file(self, monkeypatch):
+    def test_file(self, monkeypatch, keys_dir):
         monkeypatch.delenv('BORG_REPO', raising=False)
         assert repr(Location('file:///some/path::archive')) == \
             "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive='archive')"
         assert repr(Location('file:///some/path')) == \
             "Location(proto='file', user=None, host=None, port=None, path='/some/path', archive=None)"
+        assert Location('file:///some/path').to_key_filename() == keys_dir + 'some_path'
 
-    def test_scp(self, monkeypatch):
+    def test_scp(self, monkeypatch, keys_dir):
         monkeypatch.delenv('BORG_REPO', raising=False)
         assert repr(Location('user@host:/some/path::archive')) == \
             "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive='archive')"
@@ -112,42 +125,55 @@ def test_scp(self, monkeypatch):
             "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=None, path='/some/path', archive='archive')"
         assert repr(Location('user@[2001:db8::192.0.2.1]:/some/path')) == \
             "Location(proto='ssh', user='user', host='2001:db8::192.0.2.1', port=None, path='/some/path', archive=None)"
+        assert Location('user@[2001:db8::192.0.2.1]:/some/path').to_key_filename() == keys_dir + '2001_db8__192_0_2_1__some_path'
 
-    def test_smb(self, monkeypatch):
+    def test_smb(self, monkeypatch, keys_dir):
         monkeypatch.delenv('BORG_REPO', raising=False)
         assert repr(Location('file:////server/share/path::archive')) == \
             "Location(proto='file', user=None, host=None, port=None, path='//server/share/path', archive='archive')"
+        assert Location('file:////server/share/path::archive').to_key_filename() == keys_dir + 'server_share_path'
 
-    def test_folder(self, monkeypatch):
+    def test_folder(self, monkeypatch, keys_dir):
         monkeypatch.delenv('BORG_REPO', raising=False)
         assert repr(Location('path::archive')) == \
             "Location(proto='file', user=None, host=None, port=None, path='path', archive='archive')"
         assert repr(Location('path')) == \
             "Location(proto='file', user=None, host=None, port=None, path='path', archive=None)"
+        assert Location('path').to_key_filename() == keys_dir + 'path'
 
-    def test_abspath(self, monkeypatch):
+    def test_long_path(self, monkeypatch, keys_dir):
+        monkeypatch.delenv('BORG_REPO', raising=False)
+        assert Location(os.path.join(*(40 * ['path']))).to_key_filename() == keys_dir + '_'.join(20 * ['path']) + '_'
+
+    def test_abspath(self, monkeypatch, keys_dir):
         monkeypatch.delenv('BORG_REPO', raising=False)
         assert repr(Location('/some/absolute/path::archive')) == \
             "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive='archive')"
         assert repr(Location('/some/absolute/path')) == \
             "Location(proto='file', user=None, host=None, port=None, path='/some/absolute/path', archive=None)"
+        assert Location('/some/absolute/path').to_key_filename() == keys_dir + 'some_absolute_path'
         assert repr(Location('ssh://user@host/some/path')) == \
                "Location(proto='ssh', user='user', host='host', port=None, path='/some/path', archive=None)"
+        assert Location('ssh://user@host/some/path').to_key_filename() == keys_dir + 'host__some_path'
 
-    def test_relpath(self, monkeypatch):
+    def test_relpath(self, monkeypatch, keys_dir):
         monkeypatch.delenv('BORG_REPO', raising=False)
         assert repr(Location('some/relative/path::archive')) == \
             "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive='archive')"
         assert repr(Location('some/relative/path')) == \
             "Location(proto='file', user=None, host=None, port=None, path='some/relative/path', archive=None)"
+        assert Location('some/relative/path').to_key_filename() == keys_dir + 'some_relative_path'
         assert repr(Location('ssh://user@host/./some/path')) == \
                "Location(proto='ssh', user='user', host='host', port=None, path='/./some/path', archive=None)"
+        assert Location('ssh://user@host/./some/path').to_key_filename() == keys_dir + 'host__some_path'
         assert repr(Location('ssh://user@host/~/some/path')) == \
                "Location(proto='ssh', user='user', host='host', port=None, path='/~/some/path', archive=None)"
+        assert Location('ssh://user@host/~/some/path').to_key_filename() == keys_dir + 'host__some_path'
         assert repr(Location('ssh://user@host/~user/some/path')) == \
                "Location(proto='ssh', user='user', host='host', port=None, path='/~user/some/path', archive=None)"
+        assert Location('ssh://user@host/~user/some/path').to_key_filename() == keys_dir + 'host__user_some_path'
 
-    def test_with_colons(self, monkeypatch):
+    def test_with_colons(self, monkeypatch, keys_dir):
         monkeypatch.delenv('BORG_REPO', raising=False)
         assert repr(Location('/abs/path:w:cols::arch:col')) == \
             "Location(proto='file', user=None, host=None, port=None, path='/abs/path:w:cols', archive='arch:col')"
@@ -155,6 +181,7 @@ def test_with_colons(self, monkeypatch):
             "Location(proto='file', user=None, host=None, port=None, path='/abs/path:with:colons', archive='archive')"
         assert repr(Location('/abs/path:with:colons')) == \
             "Location(proto='file', user=None, host=None, port=None, path='/abs/path:with:colons', archive=None)"
+        assert Location('/abs/path:with:colons').to_key_filename() == keys_dir + 'abs_path_with_colons'
 
     def test_user_parsing(self):
         # see issue #1930

pip install -e .[fuse]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock
apt-get update apt-get install -y gcc libssl-dev libacl1-dev liblz4-dev libfuse-dev pkg-config
3.9
requirements.d/development.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
-e git+https://github.com/borgbackup/borg.git@2dcbe02e5ae2c5674b350866b145c028966e1664#egg=borgbackup
cachetools==5.5.2
chardet==5.2.0
colorama==0.4.6
coverage==7.8.0
Cython==3.0.12
distlib==0.3.9
exceptiongroup==1.2.2
execnet==2.1.1
filelock==3.18.0
iniconfig==2.1.0
llfuse==1.5.1
msgpack-python==0.5.6
packaging==24.2
platformdirs==4.3.7
pluggy==1.5.0
py-cpuinfo==9.0.0
pyproject-api==1.9.0
pytest==8.3.5
pytest-benchmark==5.1.0
pytest-cov==6.0.0
pytest-mock==3.14.0
pytest-xdist==3.6.1
setuptools-scm==8.2.0
tomli==2.2.1
tox==4.25.0
typing_extensions==4.13.0
virtualenv==20.29.3

name: borg
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - cachetools==5.5.2
      - chardet==5.2.0
      - colorama==0.4.6
      - coverage==7.8.0
      - cython==3.0.12
      - distlib==0.3.9
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - filelock==3.18.0
      - iniconfig==2.1.0
      - llfuse==1.5.1
      - msgpack-python==0.5.6
      - packaging==24.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - py-cpuinfo==9.0.0
      - pyproject-api==1.9.0
      - pytest==8.3.5
      - pytest-benchmark==5.1.0
      - pytest-cov==6.0.0
      - pytest-mock==3.14.0
      - pytest-xdist==3.6.1
      - setuptools-scm==8.2.0
      - tomli==2.2.1
      - tox==4.25.0
      - typing-extensions==4.13.0
      - virtualenv==20.29.3
prefix: /opt/conda/envs/borg

src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_ssh src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_scp src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_long_path
src/borg/testsuite/helpers.py::test_is_slow_msgpack
src/borg/testsuite/helpers.py::BigIntTestCase::test_bigint src/borg/testsuite/helpers.py::test_bin_to_hex src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_file src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_smb src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_folder src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_abspath src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_relpath src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_with_colons src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_user_parsing src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_underspecified src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_no_slashes src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_canonical_path src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_format_path src/borg/testsuite/helpers.py::TestLocationWithoutEnv::test_bad_syntax src/borg/testsuite/helpers.py::TestLocationWithEnv::test_ssh src/borg/testsuite/helpers.py::TestLocationWithEnv::test_file src/borg/testsuite/helpers.py::TestLocationWithEnv::test_scp src/borg/testsuite/helpers.py::TestLocationWithEnv::test_folder src/borg/testsuite/helpers.py::TestLocationWithEnv::test_abspath src/borg/testsuite/helpers.py::TestLocationWithEnv::test_relpath src/borg/testsuite/helpers.py::TestLocationWithEnv::test_with_colons src/borg/testsuite/helpers.py::TestLocationWithEnv::test_no_slashes src/borg/testsuite/helpers.py::FormatTimedeltaTestCase::test src/borg/testsuite/helpers.py::test_chunkerparams src/borg/testsuite/helpers.py::MakePathSafeTestCase::test src/borg/testsuite/helpers.py::PruneSplitTestCase::test src/borg/testsuite/helpers.py::PruneWithinTestCase::test src/borg/testsuite/helpers.py::StableDictTestCase::test src/borg/testsuite/helpers.py::TestParseTimestamp::test src/borg/testsuite/helpers.py::test_get_cache_dir src/borg/testsuite/helpers.py::test_get_keys_dir src/borg/testsuite/helpers.py::test_get_security_dir src/borg/testsuite/helpers.py::test_file_size src/borg/testsuite/helpers.py::test_file_size_precision src/borg/testsuite/helpers.py::test_file_size_sign src/borg/testsuite/helpers.py::test_parse_file_size[1-1] src/borg/testsuite/helpers.py::test_parse_file_size[20-20] src/borg/testsuite/helpers.py::test_parse_file_size[5K-5000] src/borg/testsuite/helpers.py::test_parse_file_size[1.75M-1750000] src/borg/testsuite/helpers.py::test_parse_file_size[1e+9-1000000000.0] src/borg/testsuite/helpers.py::test_parse_file_size[-1T--1000000000000.0] src/borg/testsuite/helpers.py::test_parse_file_size_invalid[] src/borg/testsuite/helpers.py::test_parse_file_size_invalid[5 src/borg/testsuite/helpers.py::test_parse_file_size_invalid[4E] src/borg/testsuite/helpers.py::test_parse_file_size_invalid[2229 src/borg/testsuite/helpers.py::test_parse_file_size_invalid[1B] src/borg/testsuite/helpers.py::TestBuffer::test_type src/borg/testsuite/helpers.py::TestBuffer::test_len src/borg/testsuite/helpers.py::TestBuffer::test_resize src/borg/testsuite/helpers.py::TestBuffer::test_limit src/borg/testsuite/helpers.py::TestBuffer::test_get src/borg/testsuite/helpers.py::test_yes_input src/borg/testsuite/helpers.py::test_yes_input_defaults src/borg/testsuite/helpers.py::test_yes_input_custom src/borg/testsuite/helpers.py::test_yes_env src/borg/testsuite/helpers.py::test_yes_env_default src/borg/testsuite/helpers.py::test_yes_defaults src/borg/testsuite/helpers.py::test_yes_retry src/borg/testsuite/helpers.py::test_yes_no_retry src/borg/testsuite/helpers.py::test_yes_output src/borg/testsuite/helpers.py::test_yes_env_output src/borg/testsuite/helpers.py::test_progress_percentage_sameline src/borg/testsuite/helpers.py::test_progress_percentage_step src/borg/testsuite/helpers.py::test_progress_percentage_quiet src/borg/testsuite/helpers.py::test_progress_endless src/borg/testsuite/helpers.py::test_progress_endless_step src/borg/testsuite/helpers.py::test_partial_format src/borg/testsuite/helpers.py::test_chunk_file_wrapper src/borg/testsuite/helpers.py::test_chunkit src/borg/testsuite/helpers.py::test_clean_lines src/borg/testsuite/helpers.py::test_format_line src/borg/testsuite/helpers.py::test_format_line_erroneous src/borg/testsuite/helpers.py::test_replace_placeholders src/borg/testsuite/helpers.py::test_swidth_slice src/borg/testsuite/helpers.py::test_swidth_slice_mixed_characters src/borg/testsuite/helpers.py::test_safe_timestamps src/borg/testsuite/helpers.py::TestPopenWithErrorHandling::test_simple src/borg/testsuite/helpers.py::TestPopenWithErrorHandling::test_not_found src/borg/testsuite/helpers.py::TestPopenWithErrorHandling::test_bad_syntax[mismatched src/borg/testsuite/helpers.py::TestPopenWithErrorHandling::test_bad_syntax[foo src/borg/testsuite/helpers.py::TestPopenWithErrorHandling::test_bad_syntax[] src/borg/testsuite/helpers.py::TestPopenWithErrorHandling::test_shell
src/borg/helpers.py
src/borg/helpers.py