From 844a5e0204c316dc1feeec64b4c672fc8eeade83 Mon Sep 17 00:00:00 2001 From: hyungi Date: Tue, 16 Jun 2026 13:40:35 +0900 Subject: [PATCH] =?UTF-8?q?fix(security):=20internal=20=ED=86=A0=ED=81=B0?= =?UTF-8?q?=20=EC=83=81=EC=88=98=EC=8B=9C=EA=B0=84=20=EB=B9=84=EA=B5=90=20?= =?UTF-8?q?+=20memo=20tag=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?= =?UTF-8?q?=EB=B0=94=EC=9D=B8=EB=94=A9=20(R7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - internal_study._verify_token: != 비교는 첫 불일치 단락으로 prefix 길이 timing side-channel(RAG 정답 endpoint 보호 토큰) → hmac.compare_digest(search.py 정본 일치). - memos tag 필터: f-string 으로 사용자 tag 를 JSON 배열 리터럴에 직접 삽입 → tag 안 "/] 가 JSON 깨 500 + 필터 변형. func.jsonb_build_array(tag) 바인드 파라미터로. 검증: py_compile 통과. R7 나머지(get_live_document·paper-holder deleted_at·delete_file purge 마커+retention sweep·fetch-page·save-content)는 이어서. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/api/internal_study.py | 6 +++++- app/api/memos.py | 8 ++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) 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())