فهرست منبع

Merge pull request #8894 from ThomasWaldmann/bandit

use bandit
TW 3 روز پیش
والد
کامیت
0fa548beac

+ 20 - 1
.github/workflows/ci.yml

@@ -40,9 +40,28 @@ jobs:
     - uses: actions/checkout@v4
     - uses: chartboost/ruff-action@v1
 
+  security:
+
+    runs-on: ubuntu-24.04
+    timeout-minutes: 5
+
+    steps:
+    - uses: actions/checkout@v4
+    - name: Set up Python
+      uses: actions/setup-python@v5
+      with:
+        python-version: '3.10'
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install bandit[toml]
+    - name: Run Bandit
+      run: |
+        bandit -r src/borg -c pyproject.toml
+
   linux:
 
-    needs: lint
+    needs: [lint, security]
     strategy:
       fail-fast: true
       matrix:

+ 13 - 1
pyproject.toml

@@ -159,7 +159,7 @@ ignore_missing_imports = true
 
 [tool.tox]
 requires = ["tox>=4.19", "pkgconfig", "cython", "wheel", "setuptools_scm"]
-env_list = ["py{310,311,312,313}-{none,fuse2,fuse3}", "docs", "ruff", "mypy"]
+env_list = ["py{310,311,312,313}-{none,fuse2,fuse3}", "docs", "ruff", "mypy", "bandit"]
 
 [tool.tox.env_run_base]
 package = "editable-legacy"  # without this it does not find setup_docs when running under fakeroot
@@ -195,3 +195,15 @@ commands = [["mypy", "--ignore-missing-imports"]]
 change_dir = "docs"
 deps = ["sphinx", "sphinxcontrib-jquery", "guzzle_sphinx_theme"]
 commands = [["sphinx-build", "-n", "-v", "-W", "--keep-going", "-b", "html", "-d", "{envtmpdir}/doctrees", ".", "{envtmpdir}/html"]]
+
+[tool.bandit]
+exclude_dirs = [".cache", ".eggs", ".git", ".git-rewrite", ".idea", ".mypy_cache", ".ruff_cache", ".tox", "build", "dist", "src/borg/testsuite"]
+skips = [
+    "B101",  # skip assert warnings, we do not allow running borg with assertions disabled.
+    "B404",  # do not warn about just import subprocess
+]
+
+[tool.tox.env.bandit]
+skip_install = true
+deps = ["bandit[toml]"]
+commands = [["bandit", "-r", "src/borg", "-c", "pyproject.toml"]]

+ 1 - 0
requirements.d/development.txt

@@ -12,3 +12,4 @@ pytest-cov
 pytest-benchmark
 Cython
 pre-commit
+bandit[toml]

+ 2 - 2
src/borg/archiver/create_cmd.py

@@ -73,7 +73,7 @@ class CreateMixIn:
                     try:
                         try:
                             env = prepare_subprocess_env(system=True)
-                            proc = subprocess.Popen(
+                            proc = subprocess.Popen(  # nosec B603
                                 args.paths,
                                 stdout=subprocess.PIPE,
                                 env=env,
@@ -97,7 +97,7 @@ class CreateMixIn:
                 if args.paths_from_command:
                     try:
                         env = prepare_subprocess_env(system=True)
-                        proc = subprocess.Popen(
+                        proc = subprocess.Popen(  # nosec B603
                             args.paths, stdout=subprocess.PIPE, env=env, preexec_fn=None if is_win32 else ignore_sigint
                         )
                     except (FileNotFoundError, PermissionError) as e:

+ 1 - 1
src/borg/archiver/lock_cmds.py

@@ -22,7 +22,7 @@ class LocksMixIn:
         env = prepare_subprocess_env(system=True)
         try:
             # we exit with the return code we get from the subprocess
-            rc = subprocess.call([args.command] + args.args, env=env)
+            rc = subprocess.call([args.command] + args.args, env=env)  # nosec B603
             set_ec(rc)
         except (FileNotFoundError, OSError, ValueError) as e:
             raise CommandError(f"Error while trying to run '{args.command}': {e}")

+ 3 - 2
src/borg/conftest.py

@@ -57,7 +57,7 @@ def pytest_report_header(config, start_path):
 def set_env_variables():
     os.environ["BORG_CHECK_I_KNOW_WHAT_I_AM_DOING"] = "YES"
     os.environ["BORG_DELETE_I_KNOW_WHAT_I_AM_DOING"] = "YES"
-    os.environ["BORG_PASSPHRASE"] = "waytooeasyonlyfortests"
+    os.environ["BORG_PASSPHRASE"] = "waytooeasyonlyfortests"  # nosec B105
     os.environ["BORG_SELFTEST"] = "disabled"
 
 
@@ -103,7 +103,8 @@ def archiver(tmp_path, set_env_variables):
     os.environ["BORG_KEYS_DIR"] = archiver.keys_path
     os.environ["BORG_CACHE_DIR"] = archiver.cache_path
     os.mkdir(archiver.input_path)
-    os.chmod(archiver.input_path, 0o777)  # avoid troubles with fakeroot / FUSE
+    # avoid troubles with fakeroot / FUSE:
+    os.chmod(archiver.input_path, 0o777)  # nosec B103
     os.mkdir(archiver.output_path)
     os.mkdir(archiver.keys_path)
     os.mkdir(archiver.cache_path)

+ 2 - 2
src/borg/crypto/key.py

@@ -658,7 +658,7 @@ class FlexiKey:
         elif self.STORAGE == KeyBlobStorage.REPO:
             # While the repository is encrypted, we consider a repokey repository with a blank
             # passphrase an unencrypted repository.
-            self.logically_encrypted = passphrase != ""
+            self.logically_encrypted = passphrase != ""  # nosec B105
 
             # what we get in target is just a repo location, but we already have the repo obj:
             target = self.repository
@@ -688,7 +688,7 @@ class FlexiKey:
                 fd.write(key_data)
                 fd.write("\n")
         elif self.STORAGE == KeyBlobStorage.REPO:
-            self.logically_encrypted = passphrase != ""
+            self.logically_encrypted = passphrase != ""  # nosec B105
             key_data = key_data.encode("utf-8")  # remote repo: msgpack issue #99, giving bytes
             target.save_key(key_data)
         else:

+ 2 - 2
src/borg/fslocking.py

@@ -169,11 +169,11 @@ class ExclusiveLock:
                 # should be cleaned up anyway. Try to clean up, but don't crash.
                 try:
                     os.unlink(temp_unique_name)
-                except:  # noqa
+                except:  # nosec B110 # noqa
                     pass
                 try:
                     os.rmdir(temp_path)
-                except:  # noqa
+                except:  # nosec B110 # noqa
                     pass
 
     def release(self):

+ 2 - 2
src/borg/helpers/fs.py

@@ -558,9 +558,9 @@ def umount(mountpoint):
 
     env = prepare_subprocess_env(system=True)
     try:
-        rc = subprocess.call(["fusermount", "-u", mountpoint], env=env)
+        rc = subprocess.call(["fusermount", "-u", mountpoint], env=env)  # nosec B603, B607
     except FileNotFoundError:
-        rc = subprocess.call(["umount", mountpoint], env=env)
+        rc = subprocess.call(["umount", mountpoint], env=env)  # nosec B603, B607
     set_ec(rc)
 
 

+ 1 - 1
src/borg/helpers/passphrase.py

@@ -67,7 +67,7 @@ class Passphrase(str):
             # passcommand is a system command (not inside pyinstaller env)
             env = prepare_subprocess_env(system=True)
             try:
-                passphrase = subprocess.check_output(shlex.split(passcommand), text=True, env=env)
+                passphrase = subprocess.check_output(shlex.split(passcommand), text=True, env=env)  # nosec B603
             except (subprocess.CalledProcessError, FileNotFoundError) as e:
                 raise PasscommandFailure(e)
             return cls(passphrase.rstrip("\n"))

+ 1 - 1
src/borg/helpers/process.py

@@ -286,7 +286,7 @@ def popen_with_error_handling(cmd_line: str, log_prefix="", **kwargs):
         return
     logger.debug("%scommand line: %s", log_prefix, command)
     try:
-        return subprocess.Popen(command, **kwargs)
+        return subprocess.Popen(command, **kwargs)  # nosec B603
     except FileNotFoundError:
         logger.error("%sexecutable not found: %s", log_prefix, command[0])
         return

+ 3 - 1
src/borg/legacyremote.py

@@ -275,7 +275,9 @@ class LegacyRemoteRepository:
                 borg_cmd = self.ssh_cmd(location) + borg_cmd
             logger.debug("SSH command line: %s", borg_cmd)
             # we do not want the ssh getting killed by Ctrl-C/SIGINT because it is needed for clean shutdown of borg.
-            self.p = Popen(borg_cmd, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env, preexec_fn=ignore_sigint)
+            self.p = Popen(
+                borg_cmd, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env, preexec_fn=ignore_sigint
+            )  # nosec B603
             self.stdin_fd = self.p.stdin.fileno()
             self.stdout_fd = self.p.stdout.fileno()
             self.stderr_fd = self.p.stderr.fileno()

+ 1 - 1
src/borg/platform/base.py

@@ -269,7 +269,7 @@ def getfqdn(name=""):
     An empty argument is interpreted as meaning the local host.
     """
     name = name.strip()
-    if not name or name == "0.0.0.0":
+    if not name or name == "0.0.0.0":  # nosec B104:hardcoded_bind_all_interfaces
         name = socket.gethostname()
     try:
         addrs = socket.getaddrinfo(name, None, 0, socket.SOCK_DGRAM, 0, socket.AI_CANONNAME)

+ 3 - 1
src/borg/remote.py

@@ -576,7 +576,9 @@ class RemoteRepository:
                 borg_cmd = self.ssh_cmd(location) + borg_cmd
             logger.debug("SSH command line: %s", borg_cmd)
             # we do not want the ssh getting killed by Ctrl-C/SIGINT because it is needed for clean shutdown of borg.
-            self.p = Popen(borg_cmd, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env, preexec_fn=ignore_sigint)
+            self.p = Popen(
+                borg_cmd, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env, preexec_fn=ignore_sigint
+            )  # nosec B603
             self.stdin_fd = self.p.stdin.fileno()
             self.stdout_fd = self.p.stdout.fileno()
             self.stderr_fd = self.p.stderr.fileno()

+ 3 - 1
src/borg/storelocking.py

@@ -201,7 +201,9 @@ class Lock:
                         logger.debug("LOCK-ACQUIRE: exclusive locks detected, deleting our shared lock.")
                         self._delete_lock(key, ignore_not_found=True, update_last_refresh=True)
             # wait a random bit before retrying
-            time.sleep(self.retry_delay_min + (self.retry_delay_max - self.retry_delay_min) * random.random())
+            time.sleep(
+                self.retry_delay_min + (self.retry_delay_max - self.retry_delay_min) * random.random()  # nosec B311
+            )
         logger.debug("LOCK-ACQUIRE: timeout while trying to acquire a lock.")
         raise LockTimeout(str(self.store))
 

+ 1 - 1
src/borg/xattr.py

@@ -28,7 +28,7 @@ if sys.platform.startswith("linux"):
     for preload in preloads:
         if preload.startswith("libfakeroot"):
             env = prepare_subprocess_env(system=True)
-            fakeroot_output = subprocess.check_output(["fakeroot", "-v"], env=env)
+            fakeroot_output = subprocess.check_output(["fakeroot", "-v"], env=env)  # nosec B603, B607
             fakeroot_version = parse_version(fakeroot_output.decode("ascii").split()[-1])
             if fakeroot_version >= parse_version("1.20.2"):
                 # 1.20.2 has been confirmed to have xattr support