Browse Source

Fix Bandit B314: Replace xml.etree with defusedxml

Security scan (Bandit) identified vulnerable XML parsing in 3MF file
processing. The standard xml.etree.ElementTree is vulnerable to XXE
(XML External Entity) attacks.

Changes:
- Add defusedxml>=0.7.0 to requirements.txt
- Replace all xml.etree.ElementTree imports with defusedxml.ElementTree
  in production code (6 files)

Affected files:
- backend/app/services/archive.py
- backend/app/services/print_scheduler.py
- backend/app/api/routes/print_queue.py
- backend/app/api/routes/library.py
- backend/app/api/routes/printers.py
- backend/app/api/routes/archives.py

Test files intentionally left unchanged (test XML is trusted).
maziggy 3 months ago
parent
commit
4b3b615a2c

+ 6 - 4
backend/app/api/routes/archives.py

@@ -1833,7 +1833,8 @@ async def get_archive_capabilities(
 ):
     """Check what viewing capabilities are available for this 3MF file."""
     import json
-    import xml.etree.ElementTree as ET
+
+    import defusedxml.ElementTree as ET
 
     service = ArchiveService(db)
     archive = await service.get_archive(archive_id)
@@ -2113,7 +2114,7 @@ async def get_plate_preview(
             plate_num = 1
             if "Metadata/slice_info.config" in names:
                 try:
-                    import xml.etree.ElementTree as ET
+                    import defusedxml.ElementTree as ET
 
                     slice_content = zf.read("Metadata/slice_info.config").decode("utf-8")
                     root = ET.fromstring(slice_content)
@@ -2254,7 +2255,8 @@ async def get_archive_plates(
     """
     import json
     import re
-    import xml.etree.ElementTree as ET
+
+    import defusedxml.ElementTree as ET
 
     service = ArchiveService(db)
     archive = await service.get_archive(archive_id)
@@ -2560,7 +2562,7 @@ async def get_filament_requirements(
         archive_id: The archive ID
         plate_id: Optional plate index to filter filaments for (for multi-plate files)
     """
-    import xml.etree.ElementTree as ET
+    import defusedxml.ElementTree as ET
 
     service = ArchiveService(db)
     archive = await service.get_archive(archive_id)

+ 4 - 2
backend/app/api/routes/library.py

@@ -1312,9 +1312,10 @@ async def get_library_file_plates(
     """
     import json
     import re
-    import xml.etree.ElementTree as ET
     import zipfile
 
+    import defusedxml.ElementTree as ET
+
     # Get the library file
     result = await db.execute(select(LibraryFile).where(LibraryFile.id == file_id))
     lib_file = result.scalar_one_or_none()
@@ -1613,9 +1614,10 @@ async def get_library_file_filament_requirements(
         file_id: The library file ID
         plate_id: Optional plate index to get filaments for a specific plate
     """
-    import xml.etree.ElementTree as ET
     import zipfile
 
+    import defusedxml.ElementTree as ET
+
     # Get the library file
     result = await db.execute(select(LibraryFile).where(LibraryFile.id == file_id))
     lib_file = result.scalar_one_or_none()

+ 1 - 1
backend/app/api/routes/print_queue.py

@@ -2,11 +2,11 @@
 
 import json
 import logging
-import xml.etree.ElementTree as ET
 import zipfile
 from datetime import datetime
 from pathlib import Path
 
+import defusedxml.ElementTree as ET
 from fastapi import APIRouter, Depends, HTTPException, Query
 from sqlalchemy import func, select
 from sqlalchemy.ext.asyncio import AsyncSession

+ 2 - 1
backend/app/api/routes/printers.py

@@ -852,9 +852,10 @@ async def get_printer_file_plates(
     """Get available plates from a multi-plate 3MF file stored on a printer."""
     import io
     import json
-    import xml.etree.ElementTree as ET
     import zipfile
 
+    import defusedxml.ElementTree as ET
+
     # Validate printer
     result = await db.execute(select(Printer).where(Printer.id == printer_id))
     printer = result.scalar_one_or_none()

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

@@ -6,8 +6,8 @@ import shutil
 import zipfile
 from datetime import datetime
 from pathlib import Path
-from xml.etree import ElementTree as ET
 
+from defusedxml import ElementTree as ET
 from sqlalchemy import and_, or_, select
 from sqlalchemy.ext.asyncio import AsyncSession
 

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

@@ -3,11 +3,11 @@
 import asyncio
 import json
 import logging
-import xml.etree.ElementTree as ET
 import zipfile
 from datetime import datetime, timedelta
 from pathlib import Path
 
+import defusedxml.ElementTree as ET
 from sqlalchemy import func, select
 from sqlalchemy.ext.asyncio import AsyncSession
 

+ 1 - 0
requirements.txt

@@ -20,6 +20,7 @@ pyftpdlib>=2.0.0
 cryptography>=41.0.0
 
 # 3MF Processing (standard zipfile is sufficient for Bambu 3MF files)
+defusedxml>=0.7.0  # Safe XML parsing (prevents XXE attacks)
 
 # Excel Export
 openpyxl>=3.1.0