Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@
"""Stream cleaning task log via SSE"""
import asyncio
import json
import os
import re
from pathlib import Path

Expand All @@ -300,6 +301,16 @@
if retry_count > 0:
log_path = Path(f"{FLOW_PATH}/{task_id}/output.log.{retry_count}")

# 防止路径穿越:规范化后校验仍在 /flow 下
log_path = log_path.resolve()

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
if not str(log_path).startswith(FLOW_PATH + "/"):
logger.warning(f"Path traversal attempt in stream_log: task_id={task_id}")

async def traversal_error_generator():
yield f"data: {json.dumps({'level': 'ERROR', 'message': 'Invalid task_id'}, ensure_ascii=False)}\n\n"

return StreamingResponse(traversal_error_generator(), media_type="text/event-stream")

standard_level_pattern = re.compile(
r"\b(DEBUG|Debug|INFO|Info|WARN|Warn|WARNING|Warning|ERROR|Error|FATAL|Fatal)\b"
)
Expand Down Expand Up @@ -428,6 +439,16 @@
if retry_count > 0:
log_path = Path(f"{FLOW_PATH}/{task_id}/output.log.{retry_count}")

# 防止路径穿越:规范化后校验仍在 /flow 下
log_path = log_path.resolve()

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
if not str(log_path).startswith(FLOW_PATH + "/"):
logger.warning(f"Path traversal attempt in download_log: task_id={task_id}")
from app.core.exception import BusinessError, ErrorCodes
raise BusinessError(
ErrorCodes.CLEANING_TASK_LOG_NOT_FOUND,
f"Invalid task_id: {task_id}",
)

if not log_path.exists():
from app.core.exception import BusinessError, ErrorCodes

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import os
import re
import shutil
import uuid
Expand Down Expand Up @@ -386,6 +387,12 @@
if retry_count > 0:
log_path = Path(f"{FLOW_PATH}/{task_id}/output.log.{retry_count}")

# 防止路径穿越:规范化后校验仍在 /flow 下
log_path = log_path.resolve()

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
if not str(log_path).startswith(FLOW_PATH + "/"):
logger.warning(f"Path traversal attempt detected: task_id={task_id}")
return []

if not log_path.exists():
return []

Expand Down Expand Up @@ -447,7 +454,15 @@
await self.result_repo.delete_by_instance_id(db, task_id)

# 删除任务相关文件
task_path = Path(f"{FLOW_PATH}/{task_id}")
task_path = Path(f"{FLOW_PATH}/{task_id}").resolve()

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
# 防止路径穿越:规范化后校验仍在 /flow 下
if not str(task_path).startswith(FLOW_PATH + "/"):
logger.warning(f"Path traversal attempt in delete_task: task_id={task_id}")
raise BusinessError(
ErrorCodes.CLEANING_TASK_NOT_FOUND,
f"Invalid task_id: {task_id}",
)

if task_path.exists():
try:
shutil.rmtree(task_path)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import re

from sqlalchemy.ext.asyncio import AsyncSession

from app.core.exception import BusinessError, ErrorCodes
from app.module.cleaning.schema import OperatorInstanceDto
from app.module.operator.constants import CATEGORY_DATA_JUICER_ID, CATEGORY_DATAMATE_ID

# UUID pattern for task_id validation (prevents path traversal)
_TASK_ID_PATTERN = re.compile(
r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
)


class CleaningTaskValidator:
"""Validator for cleaning tasks and templates"""
Expand Down Expand Up @@ -95,6 +102,11 @@ def check_and_get_executor_type(instances: list[OperatorInstanceDto]) -> str:

@staticmethod
def check_task_id(task_id: str) -> None:
"""Validate task ID"""
"""Validate task ID — rejects non-UUID and path traversal patterns"""
if not task_id:
raise BusinessError(ErrorCodes.CLEANING_TASK_ID_REQUIRED)
if not _TASK_ID_PATTERN.match(task_id):
raise BusinessError(
ErrorCodes.CLEANING_TASK_ID_REQUIRED,
f"Invalid task_id format: {task_id}",
)
Loading