diff --git a/app/api/internal_study.py b/app/api/internal_study.py index 2ae3cda..19c274f 100644 --- a/app/api/internal_study.py +++ b/app/api/internal_study.py @@ -6,6 +6,7 @@ Bearer token 보호 (settings.internal_worker_token). """ from __future__ import annotations +import hmac import logging from fastapi import APIRouter, Depends, Header, HTTPException, Path, Response, status @@ -28,7 +29,10 @@ def _verify_token(authorization: str | None = Header(default=None)) -> None: if not authorization or not authorization.lower().startswith("bearer "): raise HTTPException(status_code=401, detail="missing Bearer token") token = authorization[7:].strip() - if token != settings.internal_worker_token: + # 상수시간 비교 (R7) — 일반 != 는 첫 불일치에서 단락돼 prefix 길이로 바이트 추정 가능한 + # timing side-channel. 이 토큰이 RAG 정답 포함 endpoint 를 보호하므로 compare_digest 로 + # 통일(search.py 정본과 일치). + if not hmac.compare_digest(token, settings.internal_worker_token): raise HTTPException(status_code=403, detail="invalid token") diff --git a/app/api/memos.py b/app/api/memos.py index e5d22b5..e2032f9 100644 --- a/app/api/memos.py +++ b/app/api/memos.py @@ -300,9 +300,13 @@ async def list_memos( base = base.where(Document.pinned == pinned) if tag: + # 파라미터 바인딩 (R7) — f-string 으로 사용자 tag 를 JSON 배열 리터럴에 직접 삽입하면 + # tag 안 " 나 ] 가 JSON 을 깨 500 + 필터 의미 변형. jsonb_build_array 로 tag 를 + # 바인드 파라미터로 전달(@> JSONB containment). + tag_arr = func.jsonb_build_array(tag) base = base.where( - Document.user_tags.op("@>")(f'["{tag}"]') - | Document.ai_tags.op("@>")(f'["{tag}"]') + Document.user_tags.op("@>")(tag_arr) + | Document.ai_tags.op("@>")(tag_arr) ) count_query = select(func.count()).select_from(base.subquery())