Parcourir la source

Redact IP addresses from support bundle debug logs

maziggy il y a 3 mois
Parent
commit
8e6a959eef

+ 1 - 0
CHANGELOG.md

@@ -5,6 +5,7 @@ All notable changes to Bambuddy will be documented in this file.
 ## [0.2.1b3] - Unreleased
 ## [0.2.1b3] - Unreleased
 
 
 ### Fixed
 ### Fixed
+- **IP Addresses Not Redacted From Support Bundle Logs** — The `_sanitize_log_content()` function redacted emails, serials, and credentials but left raw IPv4 addresses in log output. Now adds known printer IPs to the sensitive string list for exact matching, and applies an IPv4 regex that replaces addresses with `[IP]` while preserving firmware version strings (which use leading-zero octets like `01.09.01.00`). Updated the system info page privacy disclaimer to list IP addresses as redacted.
 - **"Unknown stage (74)" on H2D During Print Preparation** — The H2D firmware reports `stg_cur=74` during print preparation, but this stage was not in the stage name lookup table (which went up to 66, sourced from BambuStudio). Now maps stage 74 to "Preparing". Also added stage 77 ("Preparing AMS") which was present in BambuStudio but missing from the lookup.
 - **"Unknown stage (74)" on H2D During Print Preparation** — The H2D firmware reports `stg_cur=74` during print preparation, but this stage was not in the stage name lookup table (which went up to 66, sourced from BambuStudio). Now maps stage 74 to "Preparing". Also added stage 77 ("Preparing AMS") which was present in BambuStudio but missing from the lookup.
 - **Wrong Documentation Link for "Lubricate Carbon Rods" on P2S** ([#490](https://github.com/maziggy/bambuddy/issues/490)) — The "Lubricate Carbon Rods" maintenance task linked to the belt tension wiki page instead of the XYZ axis lubrication page for P2S printers.
 - **Wrong Documentation Link for "Lubricate Carbon Rods" on P2S** ([#490](https://github.com/maziggy/bambuddy/issues/490)) — The "Lubricate Carbon Rods" maintenance task linked to the belt tension wiki page instead of the XYZ axis lubrication page for P2S printers.
 - **External Spool Mapping Inverted on H2C** ([#492](https://github.com/maziggy/bambuddy/issues/492)) — On H2C dual-nozzle printers, printing from the right nozzle's external spool (Ext-R) incorrectly highlighted the left external spool (Ext-L) as active. The H2C firmware reports `tray_now=254` generically for both external spools, so the frontend's direct ID comparison (`effectiveTrayNow === extTrayId`) always matched Ext-L (id=254). Now uses `active_extruder` on dual-nozzle printers to determine which external spool is active: extruder 1 (left) → Ext-L, extruder 0 (right) → Ext-R.
 - **External Spool Mapping Inverted on H2C** ([#492](https://github.com/maziggy/bambuddy/issues/492)) — On H2C dual-nozzle printers, printing from the right nozzle's external spool (Ext-R) incorrectly highlighted the left external spool (Ext-L) as active. The H2C firmware reports `tray_now=254` generically for both external spools, so the frontend's direct ID comparison (`effectiveTrayNow === extTrayId`) always matched Ext-L (id=254). Now uses `active_extruder` on dual-nozzle printers to determine which external spool is active: extruder 1 (left) → Ext-L, extruder 0 (right) → Ext-R.

+ 12 - 3
backend/app/api/routes/support.py

@@ -684,6 +684,13 @@ def _sanitize_log_content(content: str, sensitive_strings: dict[str, str] | None
     # Replace Bambu Lab printer serial numbers (format: 00M/01D/01S/01P/03W + alphanumeric, 12-16 chars total)
     # Replace Bambu Lab printer serial numbers (format: 00M/01D/01S/01P/03W + alphanumeric, 12-16 chars total)
     content = re.sub(r"\b0[0-3][A-Z0-9][A-Z0-9]{9,13}\b", "[SERIAL]", content, flags=re.IGNORECASE)
     content = re.sub(r"\b0[0-3][A-Z0-9][A-Z0-9]{9,13}\b", "[SERIAL]", content, flags=re.IGNORECASE)
 
 
+    # Replace IPv4 addresses (skip firmware versions like 01.09.01.00 which have leading zeros)
+    content = re.sub(
+        r"\b(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\b",
+        "[IP]",
+        content,
+    )
+
     # Replace paths with usernames
     # Replace paths with usernames
     content = re.sub(r"/home/[^/\s]+/", "/home/[user]/", content)
     content = re.sub(r"/home/[^/\s]+/", "/home/[user]/", content)
     content = re.sub(r"/Users/[^/\s]+/", "/Users/[user]/", content)
     content = re.sub(r"/Users/[^/\s]+/", "/Users/[user]/", content)
@@ -733,13 +740,15 @@ async def generate_support_bundle(
         # Collect known sensitive values for log redaction
         # Collect known sensitive values for log redaction
         sensitive_strings: dict[str, str] = {}
         sensitive_strings: dict[str, str] = {}
 
 
-        # Printer names and serial numbers
-        result = await db.execute(select(Printer.name, Printer.serial_number))
-        for name, serial in result.all():
+        # Printer names, serial numbers, and IP addresses
+        result = await db.execute(select(Printer.name, Printer.serial_number, Printer.ip_address))
+        for name, serial, ip_address in result.all():
             if name:
             if name:
                 sensitive_strings[name] = "[PRINTER]"
                 sensitive_strings[name] = "[PRINTER]"
             if serial:
             if serial:
                 sensitive_strings[serial] = "[SERIAL]"
                 sensitive_strings[serial] = "[SERIAL]"
+            if ip_address:
+                sensitive_strings[ip_address] = "[IP]"
 
 
         # Auth usernames
         # Auth usernames
         result = await db.execute(select(User.username))
         result = await db.execute(select(User.username))

+ 101 - 0
backend/tests/unit/test_support_helpers.py

@@ -228,6 +228,107 @@ class TestFormatBytes:
         assert _format_bytes(0) == "0 B"
         assert _format_bytes(0) == "0 B"
 
 
 
 
+class TestSanitizeLogContent:
+    """Tests for _sanitize_log_content() redaction."""
+
+    def test_ipv4_addresses_redacted(self):
+        """IPv4 addresses in log lines are replaced with [IP]."""
+        from backend.app.api.routes.support import _sanitize_log_content
+
+        content = "2024-01-15 Connected to printer at 192.168.1.100 on port 8883"
+        result = _sanitize_log_content(content)
+        assert "192.168.1.100" not in result
+        assert "[IP]" in result
+        assert "on port 8883" in result
+
+    def test_multiple_ipv4_addresses_redacted(self):
+        """Multiple different IPs in the same line are all redacted."""
+        from backend.app.api.routes.support import _sanitize_log_content
+
+        content = "Proxy 10.0.0.1 -> 192.168.1.50"
+        result = _sanitize_log_content(content)
+        assert result == "Proxy [IP] -> [IP]"
+
+    def test_firmware_versions_with_leading_zeros_preserved(self):
+        """Firmware versions like 01.09.01.00 have leading zeros and should NOT be redacted."""
+        from backend.app.api.routes.support import _sanitize_log_content
+
+        content = "Firmware version: 01.09.01.00"
+        result = _sanitize_log_content(content)
+        assert "01.09.01.00" in result
+
+    def test_firmware_version_mixed_with_ip(self):
+        """Firmware versions preserved while real IPs are redacted in the same line."""
+        from backend.app.api.routes.support import _sanitize_log_content
+
+        content = "Printer at 192.168.1.5 running firmware 01.07.02.00"
+        result = _sanitize_log_content(content)
+        assert "192.168.1.5" not in result
+        assert "01.07.02.00" in result
+        assert "[IP] running firmware 01.07.02.00" in result
+
+    def test_printer_ip_from_sensitive_strings(self):
+        """Printer IPs in sensitive_strings are replaced before regex pass."""
+        from backend.app.api.routes.support import _sanitize_log_content
+
+        content = "Connecting to 192.168.1.100"
+        result = _sanitize_log_content(content, sensitive_strings={"192.168.1.100": "[IP]"})
+        assert result == "Connecting to [IP]"
+
+    def test_edge_case_zero_ip(self):
+        """0.0.0.0 is a valid IP and should be redacted."""
+        from backend.app.api.routes.support import _sanitize_log_content
+
+        content = "Binding to 0.0.0.0"
+        result = _sanitize_log_content(content)
+        assert result == "Binding to [IP]"
+
+    def test_edge_case_broadcast_ip(self):
+        """255.255.255.255 is a valid IP and should be redacted."""
+        from backend.app.api.routes.support import _sanitize_log_content
+
+        content = "Broadcast to 255.255.255.255"
+        result = _sanitize_log_content(content)
+        assert result == "Broadcast to [IP]"
+
+    def test_invalid_octet_not_redacted(self):
+        """Octets >255 are not valid IPs and should not be redacted."""
+        from backend.app.api.routes.support import _sanitize_log_content
+
+        content = "Value 999.999.999.999"
+        result = _sanitize_log_content(content)
+        assert "999.999.999.999" in result
+
+    def test_existing_serial_redaction_still_works(self):
+        """Serial number redaction still functions alongside IP redaction."""
+        from backend.app.api.routes.support import _sanitize_log_content
+
+        content = "Printer 01SABCDEF1234 at 10.0.0.5"
+        result = _sanitize_log_content(content)
+        assert "[SERIAL]" in result
+        assert "[IP]" in result
+        assert "01SABCDEF1234" not in result
+        assert "10.0.0.5" not in result
+
+    def test_existing_email_redaction_still_works(self):
+        """Email redaction still functions alongside IP redaction."""
+        from backend.app.api.routes.support import _sanitize_log_content
+
+        content = "User user@example.com from 172.16.0.1"
+        result = _sanitize_log_content(content)
+        assert "[EMAIL]" in result
+        assert "[IP]" in result
+
+    def test_existing_path_redaction_still_works(self):
+        """Path redaction still functions alongside IP redaction."""
+        from backend.app.api.routes.support import _sanitize_log_content
+
+        content = "Config at /home/john/config.yaml from 192.168.0.1"
+        result = _sanitize_log_content(content)
+        assert "/home/[user]/" in result
+        assert "[IP]" in result
+
+
 class TestCollectSupportInfo:
 class TestCollectSupportInfo:
     """Tests for _collect_support_info() new diagnostic sections."""
     """Tests for _collect_support_info() new diagnostic sections."""
 
 

+ 2 - 1
frontend/src/pages/SystemInfoPage.tsx

@@ -341,11 +341,12 @@ export function SystemInfoPage() {
                   <li>• {t('support.notItem4', 'API keys and tokens')}</li>
                   <li>• {t('support.notItem4', 'API keys and tokens')}</li>
                   <li>• {t('support.notItem5', 'Webhook URLs')}</li>
                   <li>• {t('support.notItem5', 'Webhook URLs')}</li>
                   <li>• {t('support.notItem6', 'Your hostname or username')}</li>
                   <li>• {t('support.notItem6', 'Your hostname or username')}</li>
+                  <li>• {t('support.notItem7', 'IP addresses')}</li>
                 </ul>
                 </ul>
               </div>
               </div>
             </div>
             </div>
             <p className="text-xs text-bambu-gray/70">
             <p className="text-xs text-bambu-gray/70">
-              {t('support.privacyNote', 'Email addresses in logs are replaced with [EMAIL], printer names with [PRINTER], and serial numbers with [SERIAL].')}
+              {t('support.privacyNote', 'Email addresses in logs are replaced with [EMAIL], printer names with [PRINTER], serial numbers with [SERIAL], and IP addresses with [IP].')}
             </p>
             </p>
           </div>
           </div>
 
 

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 0
static/assets/index-DLCtwgng.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-t1Z8cVOI.js"></script>
+    <script type="module" crossorigin src="/assets/index-DLCtwgng.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-COFMpymr.css">
     <link rel="stylesheet" crossorigin href="/assets/index-COFMpymr.css">
   </head>
   </head>
   <body>
   <body>

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff