Skip to content

Commit 2cf3e8e

Browse files
committed
Make processor plugins opt-in, secure defaults
Add an explicit "Allow processor-based control" opt-in toggle and gating logic so processor plugins are only instantiated and acted on when enabled. Update UI tooltips and enable/disable behavior, add _processor_control_enabled helper, and surface a status message when processor selection is ignored. Expand PLUGIN_SYSTEM.md with usage, security guidance, registry/scan examples, and recommended opt-in semantics. Change example socket processor defaults to bind to 127.0.0.1 instead of 0.0.0.0.
1 parent 93c7c2f commit 2cf3e8e

3 files changed

Lines changed: 248 additions & 148 deletions

File tree

dlclivegui/gui/main_window.py

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,12 @@ def _build_dlc_group(self) -> QGroupBox:
417417
self.browse_processor_folder_button = QPushButton("Browse...")
418418
self.browse_processor_folder_button.setIcon(self.style().standardIcon(QStyle.StandardPixmap.SP_DirOpenIcon))
419419
self.browse_processor_folder_button.clicked.connect(self._action_browse_processor_folder)
420+
self.browse_processor_folder_button.setToolTip(
421+
"Select the folder to scan for DLC processor plugins."
422+
"<br>Plugins must inherit from dlclive.Processor, see dlclivegui.processors for more details."
423+
"<br><b>Important:</b> External processors are Python code that will be imported during discovery."
424+
"<br>Only use processor plugins from trusted sources to avoid security risks."
425+
)
420426
processor_path_layout.addWidget(self.browse_processor_folder_button)
421427

422428
self.refresh_processors_button = QPushButton("Refresh")
@@ -457,12 +463,12 @@ def _build_dlc_group(self) -> QGroupBox:
457463
self.show_predictions_checkbox.setChecked(True)
458464
form.addRow(self.show_predictions_checkbox)
459465

460-
self.auto_record_checkbox = QCheckBox("Auto-record video on processor command")
461-
self.auto_record_checkbox.setChecked(False)
462-
self.auto_record_checkbox.setToolTip(
463-
"Automatically start/stop video recording when processor receives video recording commands"
466+
self.allow_processor_ctrl_checkbox = QCheckBox("Allow processor-based control")
467+
self.allow_processor_ctrl_checkbox.setChecked(False)
468+
self.allow_processor_ctrl_checkbox.setToolTip(
469+
"If enabled, the GUI will load and interact with the selected processor plugin.\n"
464470
)
465-
form.addRow(self.auto_record_checkbox)
471+
form.addRow(self.allow_processor_ctrl_checkbox)
466472

467473
self.processor_status_label = QLabel("Processor: No clients | Recording: No")
468474
self.processor_status_label.setWordWrap(True)
@@ -958,6 +964,11 @@ def _action_open_recording_folder(self) -> None:
958964
logger.error(f"Failed to open folder: {exc}")
959965
self.statusBar().showMessage("Could not open recording folder.", 5000)
960966

967+
def _processor_control_enabled(self) -> bool:
968+
return bool(
969+
getattr(self, "allow_processor_ctrl_checkbox", None) and self.allow_processor_ctrl_checkbox.isChecked()
970+
)
971+
961972
def _refresh_processors(self) -> None:
962973
self.processor_combo.clear()
963974
self.processor_combo.addItem("No Processor", None)
@@ -1481,20 +1492,23 @@ def _configure_dlc(self) -> bool:
14811492

14821493
# Instantiate processor if selected
14831494
processor = None
1484-
selected_key = self.processor_combo.currentData()
1485-
if selected_key is not None and self._scanned_processors:
1486-
try:
1487-
# For now, instantiate with no parameters
1488-
# TODO: Add parameter dialog for processors that need params
1489-
# or pass kwargs from config ?
1490-
processor = instantiate_from_scan(self._scanned_processors, selected_key)
1491-
processor_name = self._scanned_processors[selected_key]["name"]
1492-
self.statusBar().showMessage(f"Loaded processor: {processor_name}", 3000)
1493-
except Exception as e:
1494-
error_msg = f"Failed to instantiate processor: {e}"
1495-
self._show_error(error_msg)
1496-
logger.error(error_msg)
1497-
return False
1495+
if self._processor_control_enabled():
1496+
selected_key = self.processor_combo.currentData()
1497+
if selected_key is not None and self._scanned_processors:
1498+
try:
1499+
# For now, instantiate with no parameters
1500+
processor = instantiate_from_scan(self._scanned_processors, selected_key)
1501+
processor_name = self._scanned_processors[selected_key]["name"]
1502+
self.statusBar().showMessage(f"Loaded processor: {processor_name}", 3000)
1503+
except Exception as e:
1504+
error_msg = f"Failed to instantiate processor: {e}"
1505+
self._show_error(error_msg)
1506+
logger.error(error_msg)
1507+
return False
1508+
else:
1509+
selected_key = self.processor_combo.currentData()
1510+
if selected_key is not None:
1511+
self.statusBar().showMessage(f"Processor selection ignored (control disabled): {selected_key}", 3000)
14981512

14991513
self._dlc.configure(settings, processor=processor)
15001514
self._model_path_store.save_if_valid(settings.model_path)
@@ -1508,18 +1522,24 @@ def _update_inference_buttons(self) -> None:
15081522
def _update_dlc_controls_enabled(self) -> None:
15091523
"""Enable/disable DLC settings based on inference state."""
15101524
allow_changes = not self._dlc_active
1525+
processor_controls = allow_changes and self._processor_control_enabled()
1526+
15111527
widgets = [
15121528
self.model_path_edit,
15131529
self.browse_model_button,
1530+
self.dlc_camera_combo,
1531+
# self.additional_options_edit,
1532+
]
1533+
processor_widgets = [
15141534
self.processor_folder_edit,
15151535
self.browse_processor_folder_button,
15161536
self.refresh_processors_button,
15171537
self.processor_combo,
1518-
# self.additional_options_edit,
1519-
self.dlc_camera_combo,
15201538
]
15211539
for widget in widgets:
15221540
widget.setEnabled(allow_changes)
1541+
for widget in processor_widgets:
1542+
widget.setEnabled(processor_controls)
15231543

15241544
def _update_camera_controls_enabled(self) -> None:
15251545
multi_cam_recording = self._rec_manager.is_active
@@ -1606,7 +1626,7 @@ def _update_metrics(self) -> None:
16061626
self.dlc_stats_label.setText("DLC processor idle")
16071627

16081628
# Update processor status (connection and recording state)
1609-
if hasattr(self, "processor_status_label"):
1629+
if hasattr(self, "processor_status_label") and self._processor_control_enabled():
16101630
self._update_processor_status()
16111631

16121632
# --- Recorder stats ---
@@ -1620,6 +1640,10 @@ def _update_metrics(self) -> None:
16201640

16211641
def _update_processor_status(self) -> None:
16221642
"""Update processor connection and recording status, handle auto-recording."""
1643+
if not self._processor_control_enabled():
1644+
self.processor_status_label.setText("Processor control disabled")
1645+
return
1646+
16231647
if not self._dlc_active or not self._dlc_initialized:
16241648
self.processor_status_label.setText("Processor: Not active")
16251649
return
@@ -1646,7 +1670,7 @@ def _update_processor_status(self) -> None:
16461670
self.processor_status_label.setText(f"Clients: {client_str} | Recording: {recording_str}")
16471671

16481672
# Handle auto-recording based on processor's video recording flag
1649-
if hasattr(processor, "_vid_recording") and self.auto_record_checkbox.isChecked():
1673+
if hasattr(processor, "_vid_recording") and self.allow_processor_ctrl_checkbox.isChecked():
16501674
current_vid_recording = processor.video_recording
16511675

16521676
# Check if video recording state changed

0 commit comments

Comments
 (0)