diff --git a/services/collab/documents/oneerp_documents_app/services/document_lifecycle_service.py b/services/collab/documents/oneerp_documents_app/services/document_lifecycle_service.py
index 2ac9eea..f365442 100644
--- a/services/collab/documents/oneerp_documents_app/services/document_lifecycle_service.py
+++ b/services/collab/documents/oneerp_documents_app/services/document_lifecycle_service.py
@@ -40,7 +40,8 @@
def _strip_html(html: str) -> str:
"""HTML 태그를 제거하여 플레인텍스트로 변환한다."""
- return re.sub(r"<[^>]+>", "", html).strip()
+ # 태그 본문에서 '<' 를 배제해 '<<<…' 입력의 O(n²) 역추적을 막는다.
+ return re.sub(r"<[^<>]+>", "", html).strip()
def _compute_document_hash(title: str, content: str, file_checksums: list[str]) -> str:
diff --git a/services/collab/documents/tests/unit/test_document_lifecycle_service.py b/services/collab/documents/tests/unit/test_document_lifecycle_service.py
index cb3a249..bb21b3e 100644
--- a/services/collab/documents/tests/unit/test_document_lifecycle_service.py
+++ b/services/collab/documents/tests/unit/test_document_lifecycle_service.py
@@ -6,6 +6,7 @@
from __future__ import annotations
+import time
from datetime import UTC, datetime
from unittest.mock import MagicMock
@@ -18,6 +19,10 @@
_strip_html,
)
+# 이전 정규식은 이 길이에서 O(n²) 로 수 초 걸렸다.
+_REDOS_INPUT_LEN = 100_000
+_REDOS_BUDGET_SEC = 1.0
+
class TestDocumentLifecycleService:
"""DocumentLifecycleService 테스트."""
@@ -348,6 +353,17 @@ def test_HTML_태그_제거(self) -> None:
assert _strip_html("굵은 기울임") == "굵은 기울임"
assert _strip_html("") == ""
+ def test_HTML_태그_제거_ReDoS_방지(self) -> None:
+ """'<' 반복 입력도 선형 시간에 처리한다 (py/polynomial-redos)."""
+ adversarial = "<" * _REDOS_INPUT_LEN
+
+ started = time.perf_counter()
+ result = _strip_html(adversarial)
+ elapsed = time.perf_counter() - started
+
+ assert result == adversarial
+ assert elapsed < _REDOS_BUDGET_SEC
+
def test_문서_해시_계산(self) -> None:
"""동일 입력 시 동일한 SHA-256 해시를 반환한다."""
h1 = _compute_document_hash("제목", "내용", ["checksum1"])
diff --git a/services/platform/integration-hub/oneerp_integration_hub_app/services/flow_executor_service.py b/services/platform/integration-hub/oneerp_integration_hub_app/services/flow_executor_service.py
index 893b2fe..19d37dd 100644
--- a/services/platform/integration-hub/oneerp_integration_hub_app/services/flow_executor_service.py
+++ b/services/platform/integration-hub/oneerp_integration_hub_app/services/flow_executor_service.py
@@ -10,6 +10,9 @@
logger = logging.getLogger(__name__)
+# 클라이언트용 고정 오류 문구 — 상세는 서버 로그와 integration_logs 에만 둔다.
+_FLOW_FAILED = "플로우 실행 실패"
+
class FlowExecutorService:
"""통합 플로우 실행 비즈니스 로직.
@@ -142,7 +145,7 @@ def execute_flow(self, flow_id: str) -> dict[str, Any]:
return {
"flow_id": flow_id,
"status": "failed",
- "error": error_msg,
+ "error": _FLOW_FAILED,
"duration_ms": duration_ms,
}
diff --git a/services/portal/portal_comms/oneerp_portal_comms_app/directory/services/directory_search_service.py b/services/portal/portal_comms/oneerp_portal_comms_app/directory/services/directory_search_service.py
index 6d9531c..cf29025 100644
--- a/services/portal/portal_comms/oneerp_portal_comms_app/directory/services/directory_search_service.py
+++ b/services/portal/portal_comms/oneerp_portal_comms_app/directory/services/directory_search_service.py
@@ -15,6 +15,9 @@
logger = logging.getLogger(__name__)
+# 배치 응답용 고정 문구 — 예외 문자열을 클라이언트에 흘리지 않는다.
+_ERR_PRIMARY_EXISTS = "ERR-DIR-009: 이미 주 소속이 존재합니다"
+
class DirectorySearchService:
"""인명부 검색 비즈니스 로직.
@@ -202,8 +205,8 @@ def batch_register(
# DB 기존 주 소속 중복 검사
try:
self.validate_primary_assignment(employee_id)
- except ValueError as e:
- errors.append({"index": idx, "error": str(e)})
+ except ValueError:
+ errors.append({"index": idx, "error": _ERR_PRIMARY_EXISTS})
continue
entry["tenant_id"] = self._tenant_id
diff --git a/services/sales/commerce/oneerp_commerce_app/ecommerce/services/order_sync_service.py b/services/sales/commerce/oneerp_commerce_app/ecommerce/services/order_sync_service.py
index 8718757..4c38e59 100644
--- a/services/sales/commerce/oneerp_commerce_app/ecommerce/services/order_sync_service.py
+++ b/services/sales/commerce/oneerp_commerce_app/ecommerce/services/order_sync_service.py
@@ -13,6 +13,8 @@
logger = logging.getLogger(__name__)
_MPO_PREFIX = "MPO"
+# 클라이언트용 고정 오류 문구 — 예외 상세는 서버 로그에만 남긴다.
+_SYNC_FAILED = "주문 저장 실패"
class OrderSyncService:
@@ -84,8 +86,8 @@ def sync_orders(
}
self._order_repo.insert(doc)
created += 1
- except Exception as e:
- errors.append(f"{ext_id}: {e}")
+ except Exception:
+ errors.append(f"{ext_id}: {_SYNC_FAILED}")
logger.exception("주문 동기화 실패: %s", ext_id)
logger.info(