-
Notifications
You must be signed in to change notification settings - Fork 0
Fix security vulnerabilities identified in PR review #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -196,7 +196,22 @@ def create_qr_image( | |
|
|
||
| Returns: | ||
| str: Path to generated QR code image / 생성된 QR 코드 이미지 경로 | ||
|
|
||
| Raises: | ||
| ValueError: If output_dir is invalid / output_dir가 유효하지 않을 경우 | ||
| """ | ||
| # Validate and create output directory if it doesn't exist | ||
| # 출력 디렉토리가 존재하지 않으면 검증 및 생성 | ||
| if not output_dir: | ||
| raise ValueError("output_dir cannot be empty / output_dir는 비워둘 수 없습니다") | ||
|
|
||
| if not os.path.isdir(output_dir): | ||
| try: | ||
| os.makedirs(output_dir, exist_ok=True) | ||
| self.logger.info(f"Created output directory: {output_dir}") | ||
| except OSError as e: | ||
| raise ValueError(f"Failed to create output directory: {output_dir}. Error: {e}") | ||
|
Comment on lines
+208
to
+213
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
try:
os.makedirs(output_dir, exist_ok=True)
self.logger.info(f"Created output directory: {output_dir}")
except OSError as e:
raise ValueError(f"Failed to create output directory: {output_dir}. Error: {e}") |
||
|
|
||
| # Create QR code | ||
| # QR 코드 생성 | ||
| qr_data = key.to_json(include_utc=include_utc) | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -12,6 +12,8 @@ | |||||||||||||
| import subprocess | ||||||||||||||
| import sys | ||||||||||||||
| import logging | ||||||||||||||
| import threading | ||||||||||||||
| import time | ||||||||||||||
| from typing import Optional, Callable | ||||||||||||||
| from datetime import datetime | ||||||||||||||
| from dataclasses import dataclass | ||||||||||||||
|
|
@@ -21,6 +23,12 @@ | |||||||||||||
| from constants import DEFAULT_KEY_FILE, DEFAULT_LOG_FILE | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| # Constants for process monitoring | ||||||||||||||
| # 프로세스 모니터링 상수 | ||||||||||||||
| PROCESS_CHECK_INTERVAL_SECONDS = 5 | ||||||||||||||
| PROCESS_TERMINATION_TIMEOUT = 5 | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| @dataclass | ||||||||||||||
| class SubscriberConfig: | ||||||||||||||
| """ | ||||||||||||||
|
|
@@ -32,6 +40,7 @@ class SubscriberConfig: | |||||||||||||
| key_file: str = DEFAULT_KEY_FILE | ||||||||||||||
| log_file: str = DEFAULT_LOG_FILE | ||||||||||||||
| scanner_script: str = "qr_scanner.py" | ||||||||||||||
| monitor_scanner: bool = True # Enable scanner process monitoring / 스캐너 프로세스 모니터링 활성화 | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| class DoorLensSubscriber: | ||||||||||||||
|
|
@@ -41,15 +50,17 @@ class DoorLensSubscriber: | |||||||||||||
|
|
||||||||||||||
| Features: | ||||||||||||||
| - Secure subprocess execution (no shell injection) | ||||||||||||||
| - Process lifecycle management | ||||||||||||||
| - Process lifecycle management with health monitoring | ||||||||||||||
| - Graceful shutdown handling | ||||||||||||||
| - Message deduplication | ||||||||||||||
| - Scanner process crash detection and logging | ||||||||||||||
|
|
||||||||||||||
| 기능: | ||||||||||||||
| - 안전한 서브프로세스 실행 (쉘 인젝션 없음) | ||||||||||||||
| - 프로세스 수명 관리 | ||||||||||||||
| - 상태 모니터링을 포함한 프로세스 수명 관리 | ||||||||||||||
| - 정상적인 종료 처리 | ||||||||||||||
| - 메시지 중복 제거 | ||||||||||||||
| - 스캐너 프로세스 충돌 감지 및 로깅 | ||||||||||||||
| """ | ||||||||||||||
|
|
||||||||||||||
| def __init__( | ||||||||||||||
|
|
@@ -76,6 +87,8 @@ def __init__( | |||||||||||||
| self._scanner_process: Optional[subprocess.Popen] = None | ||||||||||||||
| self._running = False | ||||||||||||||
| self._last_message_id: Optional[str] = None | ||||||||||||||
| self._monitor_thread: Optional[threading.Thread] = None | ||||||||||||||
| self._monitor_running = False | ||||||||||||||
|
|
||||||||||||||
| self._register_signal_handlers() | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -108,15 +121,87 @@ def signal_handler(signum, frame): | |||||||||||||
| signal.signal(signal.SIGTERM, signal_handler) | ||||||||||||||
| signal.signal(signal.SIGINT, signal_handler) | ||||||||||||||
|
|
||||||||||||||
| def _stop_process_monitor(self) -> None: | ||||||||||||||
| """ | ||||||||||||||
| Stop the process monitor thread. | ||||||||||||||
| 프로세스 모니터 스레드를 중지합니다. | ||||||||||||||
| """ | ||||||||||||||
| self._monitor_running = False | ||||||||||||||
| if self._monitor_thread is not None and self._monitor_thread.is_alive(): | ||||||||||||||
| self._monitor_thread.join(timeout=2) | ||||||||||||||
| self._monitor_thread = None | ||||||||||||||
|
Comment on lines
+130
to
+132
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 프로세스 모니터 스레드를 중지할 때
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| def _start_process_monitor(self) -> None: | ||||||||||||||
| """ | ||||||||||||||
| Start a background thread to monitor the scanner process health. | ||||||||||||||
| 스캐너 프로세스 상태를 모니터링하는 백그라운드 스레드를 시작합니다. | ||||||||||||||
| """ | ||||||||||||||
| if not self.config.monitor_scanner: | ||||||||||||||
| return | ||||||||||||||
|
|
||||||||||||||
| self._stop_process_monitor() | ||||||||||||||
| self._monitor_running = True | ||||||||||||||
| self._monitor_thread = threading.Thread( | ||||||||||||||
| target=self._monitor_scanner_process, | ||||||||||||||
| daemon=True, | ||||||||||||||
| name="ScannerProcessMonitor" | ||||||||||||||
| ) | ||||||||||||||
| self._monitor_thread.start() | ||||||||||||||
| self.logger.debug("Process monitor thread started") | ||||||||||||||
|
|
||||||||||||||
| def _monitor_scanner_process(self) -> None: | ||||||||||||||
| """ | ||||||||||||||
| Monitor scanner process and log if it crashes. | ||||||||||||||
| 스캐너 프로세스를 모니터링하고 충돌 시 로그를 기록합니다. | ||||||||||||||
|
|
||||||||||||||
| This runs in a background thread and checks process status periodically. | ||||||||||||||
| 백그라운드 스레드에서 실행되며 주기적으로 프로세스 상태를 확인합니다. | ||||||||||||||
| """ | ||||||||||||||
| while self._monitor_running and self._scanner_process is not None: | ||||||||||||||
| time.sleep(PROCESS_CHECK_INTERVAL_SECONDS) | ||||||||||||||
|
|
||||||||||||||
| if self._scanner_process is None: | ||||||||||||||
| break | ||||||||||||||
|
|
||||||||||||||
| # Check if process has terminated | ||||||||||||||
| # 프로세스가 종료되었는지 확인 | ||||||||||||||
| return_code = self._scanner_process.poll() | ||||||||||||||
| if return_code is not None: | ||||||||||||||
| # Process has terminated | ||||||||||||||
| # 프로세스가 종료됨 | ||||||||||||||
| if return_code == 0: | ||||||||||||||
| self.logger.info("Scanner process exited normally (code 0)") | ||||||||||||||
| else: | ||||||||||||||
| self.logger.error( | ||||||||||||||
| f"Scanner process crashed unexpectedly (exit code: {return_code})" | ||||||||||||||
| ) | ||||||||||||||
| # Try to capture stderr for debugging | ||||||||||||||
| # 디버깅을 위해 stderr 캡처 시도 | ||||||||||||||
| try: | ||||||||||||||
| if self._scanner_process.stderr: | ||||||||||||||
| stderr_output = self._scanner_process.stderr.read() | ||||||||||||||
| if stderr_output: | ||||||||||||||
| stderr_text = stderr_output.decode('utf-8', errors='replace') | ||||||||||||||
| self.logger.error(f"Scanner stderr: {stderr_text[:500]}") | ||||||||||||||
| except Exception as e: | ||||||||||||||
| self.logger.debug(f"Could not read scanner stderr: {e}") | ||||||||||||||
|
|
||||||||||||||
| self._scanner_process = None | ||||||||||||||
| break | ||||||||||||||
|
|
||||||||||||||
| def _stop_scanner_process(self) -> None: | ||||||||||||||
| """ | ||||||||||||||
| Stop any running scanner process. | ||||||||||||||
| 실행 중인 스캐너 프로세스를 중지합니다. | ||||||||||||||
| """ | ||||||||||||||
| # Stop the monitor first | ||||||||||||||
| # 먼저 모니터 중지 | ||||||||||||||
| self._stop_process_monitor() | ||||||||||||||
|
|
||||||||||||||
| if self._scanner_process is not None: | ||||||||||||||
| try: | ||||||||||||||
| self._scanner_process.terminate() | ||||||||||||||
| self._scanner_process.wait(timeout=5) | ||||||||||||||
| self._scanner_process.wait(timeout=PROCESS_TERMINATION_TIMEOUT) | ||||||||||||||
| self.logger.info("Scanner process terminated") | ||||||||||||||
| except subprocess.TimeoutExpired: | ||||||||||||||
| self._scanner_process.kill() | ||||||||||||||
|
|
@@ -158,6 +243,11 @@ def _start_scanner_process(self) -> bool: | |||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| self.logger.info(f"Scanner process started (PID: {self._scanner_process.pid})") | ||||||||||||||
|
|
||||||||||||||
| # Start process monitor | ||||||||||||||
| # 프로세스 모니터 시작 | ||||||||||||||
| self._start_process_monitor() | ||||||||||||||
|
|
||||||||||||||
| return True | ||||||||||||||
|
|
||||||||||||||
| except Exception as e: | ||||||||||||||
|
|
@@ -183,6 +273,10 @@ def _save_key(self, key_data: str) -> bool: | |||||||||||||
| with open(self.config.key_file, 'w') as f: | ||||||||||||||
| f.write(key_data) | ||||||||||||||
|
|
||||||||||||||
| # Set restrictive file permissions (owner read/write only) | ||||||||||||||
| # 제한적 파일 권한 설정 (소유자 읽기/쓰기만 허용) | ||||||||||||||
| os.chmod(self.config.key_file, 0o600) | ||||||||||||||
|
|
||||||||||||||
| self.logger.info(f"Key saved to {self.config.key_file}") | ||||||||||||||
| return True | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
경로 탐색(Path Traversal) 방지 로직이 올바르게 작동하지만, 중첩된
if문으로 인해 다소 복잡하고 이해하기 어렵습니다. 또한, 심볼릭 링크를 처리하기 위해os.path.abspath대신os.path.realpath를 사용하는 것이 더 안전합니다. 가독성을 높이고 코드를 더 명확하게 만들기 위해 로직을 단순화하는 것을 제안합니다.