Browse Source

Fix safe security findings: hashlib, log injection, broad excepts, bandit suppressions

- Add usedforsecurity=False to MD5 (AMS fingerprint) and SHA1 (git blob
  hash) calls to silence Bandit B303 / CodeQL weak-crypto findings
- Convert ~996 f-string logging calls to parameterized %s-style across
  55 files to prevent log injection (Bandit G201 / CodeQL log-injection)
- Narrow ~199 broad except Exception blocks to specific types:
  OperationalError for DB migrations, OSError for network/file cleanup,
  (OSError, ftplib.error_reply) for FTP, and targeted tuples for
  ZIP/XML/JSON parsing — 36 intentionally left broad (mixed async,
  re-raise patterns)
- Add # nosec comments to 9 known-safe lines (0.0.0.0 virtual printer
  binds, ftplib imports) and exclude backend/tests/ from bandit scan
- Bandit now reports 0 medium/high findings
maziggy 3 months ago
parent
commit
a0133fb43b

+ 1 - 1
backend/app/main.py

@@ -76,7 +76,7 @@ def _start_error_server(missing_packages: list):
     print(f"\nStarting error server on http://0.0.0.0:{port}")
     print(f"\nStarting error server on http://0.0.0.0:{port}")
     print("Visit this URL in your browser to see the error details.\n")
     print("Visit this URL in your browser to see the error details.\n")
 
 
-    server = HTTPServer(("0.0.0.0", port), ErrorHandler)
+    server = HTTPServer(("0.0.0.0", port), ErrorHandler)  # nosec B104 - intentional bind-all for container
 
 
     def shutdown(signum, frame):
     def shutdown(signum, frame):
         print("\nShutting down error server...")
         print("\nShutting down error server...")

+ 2 - 2
backend/app/services/bambu_ftp.py

@@ -1,11 +1,11 @@
 import asyncio
 import asyncio
-import ftplib
+import ftplib  # nosec B402 - FTP required by Bambu Lab printer protocol
 import logging
 import logging
 import os
 import os
 import socket
 import socket
 import ssl
 import ssl
 from collections.abc import Awaitable, Callable
 from collections.abc import Awaitable, Callable
-from ftplib import FTP, FTP_TLS
+from ftplib import FTP, FTP_TLS  # nosec B402
 from io import BytesIO
 from io import BytesIO
 from pathlib import Path
 from pathlib import Path
 from typing import TypeVar
 from typing import TypeVar

+ 1 - 1
backend/app/services/external_camera.py

@@ -59,7 +59,7 @@ def _sanitize_camera_url(url: str, allowed_schemes: tuple[str, ...] = ("http", "
             "localhost",  # Block localhost to prevent internal service access
             "localhost",  # Block localhost to prevent internal service access
             "127.0.0.1",
             "127.0.0.1",
             "::1",
             "::1",
-            "0.0.0.0",
+            "0.0.0.0",  # nosec B104 - SSRF blocklist, not a bind
         )
         )
         if hostname_lower in blocked_hosts:
         if hostname_lower in blocked_hosts:
             logger.warning("Blocked camera URL targeting restricted host: %s", hostname)
             logger.warning("Blocked camera URL targeting restricted host: %s", hostname)

+ 3 - 3
backend/app/services/virtual_printer/ftp_server.py

@@ -217,7 +217,7 @@ class FTPSession:
             # Create data server with TLS - use same context for session reuse
             # Create data server with TLS - use same context for session reuse
             self.data_server = await asyncio.start_server(
             self.data_server = await asyncio.start_server(
                 self._handle_data_connection,
                 self._handle_data_connection,
-                "0.0.0.0",
+                "0.0.0.0",  # nosec B104 - virtual printer proxy
                 self.data_port,
                 self.data_port,
                 ssl=self.ssl_context,
                 ssl=self.ssl_context,
             )
             )
@@ -251,7 +251,7 @@ class FTPSession:
             # Create data server with TLS
             # Create data server with TLS
             self.data_server = await asyncio.start_server(
             self.data_server = await asyncio.start_server(
                 self._handle_data_connection,
                 self._handle_data_connection,
-                "0.0.0.0",
+                "0.0.0.0",  # nosec B104 - virtual printer proxy
                 self.data_port,
                 self.data_port,
                 ssl=self.ssl_context,
                 ssl=self.ssl_context,
             )
             )
@@ -514,7 +514,7 @@ class VirtualPrinterFTPServer:
             # Create server with SSL - TLS handshake happens before any FTP data
             # Create server with SSL - TLS handshake happens before any FTP data
             self._server = await asyncio.start_server(
             self._server = await asyncio.start_server(
                 self._handle_client,
                 self._handle_client,
-                "0.0.0.0",
+                "0.0.0.0",  # nosec B104 - virtual printer proxy
                 self.port,
                 self.port,
                 ssl=self._ssl_context,  # This makes it implicit FTPS!
                 ssl=self._ssl_context,  # This makes it implicit FTPS!
             )
             )

+ 1 - 1
backend/app/services/virtual_printer/mqtt_server.py

@@ -250,7 +250,7 @@ class SimpleMQTTServer:
 
 
             self._server = await asyncio.start_server(
             self._server = await asyncio.start_server(
                 connection_handler,
                 connection_handler,
-                "0.0.0.0",
+                "0.0.0.0",  # nosec B104 - virtual printer proxy
                 self.port,
                 self.port,
                 ssl=ssl_context,
                 ssl=ssl_context,
             )
             )

+ 1 - 1
backend/app/services/virtual_printer/tcp_proxy.py

@@ -102,7 +102,7 @@ class TLSProxy:
             # Start server with TLS
             # Start server with TLS
             self._server = await asyncio.start_server(
             self._server = await asyncio.start_server(
                 self._handle_client,
                 self._handle_client,
-                "0.0.0.0",
+                "0.0.0.0",  # nosec B104 - virtual printer proxy
                 self.listen_port,
                 self.listen_port,
                 ssl=self._server_ssl_context,
                 ssl=self._server_ssl_context,
             )
             )

+ 1 - 1
test_security.sh

@@ -118,7 +118,7 @@ scan_bandit() {
         echo "SKIP: 'bandit' not found. Install: pip install bandit[sarif]"
         echo "SKIP: 'bandit' not found. Install: pip install bandit[sarif]"
         return 2
         return 2
     fi
     fi
-    bandit -r backend/ --severity-level medium 2>&1
+    bandit -r backend/ --severity-level medium -x backend/tests 2>&1
 }
 }
 
 
 scan_codeql_python() {
 scan_codeql_python() {