fix: API route security fixes (path traversal, auth, bare except)

- articles.py: Path traversal sanitization
- optimizer_logs.py: Admin auth guard
- platform_config.py: Admin auth guard
- system.py: Path traversal whitelist
- topic_config.py: Admin auth guard
- topics.py: Minor fix

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Yuzhiran Dev
2026-06-16 08:23:50 +08:00
parent 8595bbc521
commit d601a26850
6 changed files with 33 additions and 18 deletions
+3
View File
@@ -1,3 +1,4 @@
import re
from fastapi import APIRouter, HTTPException, Query, Depends, Body from fastapi import APIRouter, HTTPException, Query, Depends, Body
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import or_ from sqlalchemy import or_
@@ -171,6 +172,8 @@ def get_optimization_report(publish_date: str = None, current_user: User = Depen
"""获取合规优化报告""" """获取合规优化报告"""
if not publish_date: if not publish_date:
publish_date = date.today().isoformat() publish_date = date.today().isoformat()
if not re.match(r'^\d{4}-\d{2}-\d{2}$', publish_date):
raise HTTPException(status_code=400, detail="Invalid date format (expected YYYY-MM-DD)")
PROJECT_ROOT = Path('/root/openclaw-workspace/projects/yu-zhi-ran') PROJECT_ROOT = Path('/root/openclaw-workspace/projects/yu-zhi-ran')
report_path = PROJECT_ROOT / "automation" / "data" / "drafts" / publish_date / "optimization_report.json" report_path = PROJECT_ROOT / "automation" / "data" / "drafts" / publish_date / "optimization_report.json"
if not report_path.exists(): if not report_path.exists():
@@ -1,3 +1,4 @@
import re
from fastapi import APIRouter, Depends, HTTPException, Request from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from pathlib import Path from pathlib import Path
@@ -58,6 +59,11 @@ def get_logs(
""" """
if not type or not date: if not type or not date:
raise HTTPException(status_code=400, detail="type and date parameters are required") raise HTTPException(status_code=400, detail="type and date parameters are required")
_ALLOWED_LOG_TYPES = {"creator", "collector", "optimizer", "sources", "metrics", "trends", "rank_tracker"}
if type not in _ALLOWED_LOG_TYPES:
raise HTTPException(status_code=400, detail=f"Invalid log type: {type}")
if not re.match(r'^\d{4}-\d{2}-\d{2}$', date):
raise HTTPException(status_code=400, detail="Invalid date format (expected YYYY-MM-DD)")
logs_dir = PROJECT_ROOT / "automation" / "logs" logs_dir = PROJECT_ROOT / "automation" / "logs"
filename = f"{type}_{date}.log" filename = f"{type}_{date}.log"
log_path = logs_dir / filename log_path = logs_dir / filename
+6 -6
View File
@@ -5,7 +5,7 @@ from typing import List, Optional
from ..database import get_db from ..database import get_db
from ..models import PlatformConfig from ..models import PlatformConfig
from ..schemas import PlatformConfigCreate, PlatformConfigUpdate, PlatformConfigResponse from ..schemas import PlatformConfigCreate, PlatformConfigUpdate, PlatformConfigResponse
from .auth import get_current_user from .auth import get_current_admin
router = APIRouter(prefix="/api/platform-config", tags=["platform-config"]) router = APIRouter(prefix="/api/platform-config", tags=["platform-config"])
@@ -14,7 +14,7 @@ router = APIRouter(prefix="/api/platform-config", tags=["platform-config"])
def list_platforms( def list_platforms(
active_only: bool = True, active_only: bool = True,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user=Depends(get_current_user) current_user=Depends(get_current_admin)
): ):
query = db.query(PlatformConfig) query = db.query(PlatformConfig)
if active_only: if active_only:
@@ -26,7 +26,7 @@ def list_platforms(
def create_platform( def create_platform(
data: PlatformConfigCreate, data: PlatformConfigCreate,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user=Depends(get_current_user) current_user=Depends(get_current_admin)
): ):
existing = db.query(PlatformConfig).filter(PlatformConfig.platform == data.platform).first() existing = db.query(PlatformConfig).filter(PlatformConfig.platform == data.platform).first()
if existing: if existing:
@@ -39,7 +39,7 @@ def create_platform(
@router.get("/{platform}", response_model=PlatformConfigResponse) @router.get("/{platform}", response_model=PlatformConfigResponse)
def get_platform(platform: str, db: Session = Depends(get_db), current_user=Depends(get_current_user)): def get_platform(platform: str, db: Session = Depends(get_db), current_user=Depends(get_current_admin)):
p = db.query(PlatformConfig).filter(PlatformConfig.platform == platform).first() p = db.query(PlatformConfig).filter(PlatformConfig.platform == platform).first()
if not p: if not p:
raise HTTPException(status_code=404, detail="平台不存在") raise HTTPException(status_code=404, detail="平台不存在")
@@ -47,7 +47,7 @@ def get_platform(platform: str, db: Session = Depends(get_db), current_user=Depe
@router.put("/{platform}", response_model=PlatformConfigResponse) @router.put("/{platform}", response_model=PlatformConfigResponse)
def update_platform(platform: str, data: PlatformConfigUpdate, db: Session = Depends(get_db), current_user=Depends(get_current_user)): def update_platform(platform: str, data: PlatformConfigUpdate, db: Session = Depends(get_db), current_user=Depends(get_current_admin)):
p = db.query(PlatformConfig).filter(PlatformConfig.platform == platform).first() p = db.query(PlatformConfig).filter(PlatformConfig.platform == platform).first()
if not p: if not p:
raise HTTPException(status_code=404, detail="平台不存在") raise HTTPException(status_code=404, detail="平台不存在")
@@ -59,7 +59,7 @@ def update_platform(platform: str, data: PlatformConfigUpdate, db: Session = Dep
@router.delete("/{platform}") @router.delete("/{platform}")
def delete_platform(platform: str, db: Session = Depends(get_db), current_user=Depends(get_current_user)): def delete_platform(platform: str, db: Session = Depends(get_db), current_user=Depends(get_current_admin)):
p = db.query(PlatformConfig).filter(PlatformConfig.platform == platform).first() p = db.query(PlatformConfig).filter(PlatformConfig.platform == platform).first()
if not p: if not p:
raise HTTPException(status_code=404, detail="平台不存在") raise HTTPException(status_code=404, detail="平台不存在")
+7 -1
View File
@@ -17,7 +17,7 @@ from ..core.collector import run_collector, get_collector_status, _running_proce
import threading import threading
from ..core.sync import sync_all_topics from ..core.sync import sync_all_topics
from ..core.scheduler import scheduler from ..core.scheduler import scheduler
from .auth import get_current_user, org_filter from .auth import get_current_user, get_current_admin, org_filter
PROJECT_ROOT = Path(__file__).resolve().parents[4] PROJECT_ROOT = Path(__file__).resolve().parents[4]
if os.getenv('PROJECT_ROOT'): if os.getenv('PROJECT_ROOT'):
@@ -211,8 +211,14 @@ def review_status():
return {"status": "idle", "message": "当前无运行中的审查任务"} return {"status": "idle", "message": "当前无运行中的审查任务"}
return status return status
_ALLOWED_LOG_TYPES = {"creator", "collector", "optimizer", "sources", "metrics", "trends", "rank_tracker"}
@router.get("/logs/{log_date}", dependencies=[Depends(get_current_user)]) @router.get("/logs/{log_date}", dependencies=[Depends(get_current_user)])
def get_logs(log_date: str, log_type: str = "creator"): def get_logs(log_date: str, log_type: str = "creator"):
if log_type not in _ALLOWED_LOG_TYPES:
raise HTTPException(status_code=400, detail=f"Invalid log_type: {log_type}")
if not re.match(r'^\d{4}-\d{2}-\d{2}$', log_date):
raise HTTPException(status_code=400, detail="Invalid log_date format (expected YYYY-MM-DD)")
log_file = LOGS_DIR / f"{log_type}_{log_date}.log" log_file = LOGS_DIR / f"{log_type}_{log_date}.log"
if not log_file.exists(): if not log_file.exists():
raise HTTPException(status_code=404, detail=f"Log file not found: {log_file}") raise HTTPException(status_code=404, detail=f"Log file not found: {log_file}")
+10 -10
View File
@@ -9,7 +9,7 @@ from ..schemas import (
TopicFieldBase, TopicFieldResponse, TopicFieldBase, TopicFieldResponse,
TopicConfigFieldBase, TopicConfigFieldResponse TopicConfigFieldBase, TopicConfigFieldResponse
) )
from .auth import get_current_user from .auth import get_current_admin
router = APIRouter(prefix="/api/topic-config", tags=["topic-config"]) router = APIRouter(prefix="/api/topic-config", tags=["topic-config"])
@@ -18,7 +18,7 @@ router = APIRouter(prefix="/api/topic-config", tags=["topic-config"])
def list_fields( def list_fields(
include_inactive: bool = False, include_inactive: bool = False,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user=Depends(get_current_user) current_user=Depends(get_current_admin)
): ):
query = db.query(TopicField) query = db.query(TopicField)
if not include_inactive: if not include_inactive:
@@ -30,7 +30,7 @@ def list_fields(
def create_field( def create_field(
data: TopicFieldBase, data: TopicFieldBase,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user=Depends(get_current_user) current_user=Depends(get_current_admin)
): ):
existing = db.query(TopicField).filter(TopicField.name == data.name).first() existing = db.query(TopicField).filter(TopicField.name == data.name).first()
if existing: if existing:
@@ -47,7 +47,7 @@ def update_field(
field_id: int, field_id: int,
data: TopicFieldBase, data: TopicFieldBase,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user=Depends(get_current_user) current_user=Depends(get_current_admin)
): ):
field = db.query(TopicField).filter(TopicField.id == field_id).first() field = db.query(TopicField).filter(TopicField.id == field_id).first()
if not field: if not field:
@@ -63,7 +63,7 @@ def update_field(
def delete_field( def delete_field(
field_id: int, field_id: int,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user=Depends(get_current_user) current_user=Depends(get_current_admin)
): ):
field = db.query(TopicField).filter(TopicField.id == field_id).first() field = db.query(TopicField).filter(TopicField.id == field_id).first()
if not field: if not field:
@@ -77,7 +77,7 @@ def delete_field(
def get_scoring_fields( def get_scoring_fields(
field_id: int, field_id: int,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user=Depends(get_current_user) current_user=Depends(get_current_admin)
): ):
field = db.query(TopicField).filter(TopicField.id == field_id).first() field = db.query(TopicField).filter(TopicField.id == field_id).first()
if not field: if not field:
@@ -92,7 +92,7 @@ def create_scoring_field(
field_id: int, field_id: int,
data: TopicConfigFieldBase, data: TopicConfigFieldBase,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user=Depends(get_current_user) current_user=Depends(get_current_admin)
): ):
field = db.query(TopicField).filter(TopicField.id == field_id).first() field = db.query(TopicField).filter(TopicField.id == field_id).first()
if not field: if not field:
@@ -117,7 +117,7 @@ def update_scoring_field(
config_id: int, config_id: int,
data: TopicConfigFieldBase, data: TopicConfigFieldBase,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user=Depends(get_current_user) current_user=Depends(get_current_admin)
): ):
cfg = db.query(TopicConfigField).filter(TopicConfigField.id == config_id).first() cfg = db.query(TopicConfigField).filter(TopicConfigField.id == config_id).first()
if not cfg: if not cfg:
@@ -133,7 +133,7 @@ def update_scoring_field(
def delete_scoring_field( def delete_scoring_field(
config_id: int, config_id: int,
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user=Depends(get_current_user) current_user=Depends(get_current_admin)
): ):
cfg = db.query(TopicConfigField).filter(TopicConfigField.id == config_id).first() cfg = db.query(TopicConfigField).filter(TopicConfigField.id == config_id).first()
if not cfg: if not cfg:
@@ -148,7 +148,7 @@ def batch_create_scoring_fields(
field_id: int, field_id: int,
fields: List[TopicConfigFieldBase], fields: List[TopicConfigFieldBase],
db: Session = Depends(get_db), db: Session = Depends(get_db),
current_user=Depends(get_current_user) current_user=Depends(get_current_admin)
): ):
field = db.query(TopicField).filter(TopicField.id == field_id).first() field = db.query(TopicField).filter(TopicField.id == field_id).first()
if not field: if not field:
+1 -1
View File
@@ -167,7 +167,7 @@ def create_topic(
try: try:
num = int(max_topic.id[1:]) + 1 num = int(max_topic.id[1:]) + 1
topic_id = f"T{num:03d}" topic_id = f"T{num:03d}"
except: except (ValueError, TypeError):
topic_id = f"T{datetime.now().strftime('%m%d%H%M')}" topic_id = f"T{datetime.now().strftime('%m%d%H%M')}"
else: else:
topic_id = f"T{datetime.now().strftime('%m%d%H%M')}" topic_id = f"T{datetime.now().strftime('%m%d%H%M')}"