diff --git a/Caddyfile b/Caddyfile index 06dbb83..45cc567 100644 --- a/Caddyfile +++ b/Caddyfile @@ -12,6 +12,13 @@ http://document.hyungi.net { # 명시 Content-Type match — 기본 match 의 text/* 는 text/event-stream 까지 포함해 # SSE(/api/eid/chat)의 첫 ~512B 를 gzip 버퍼링함. SSE 제외, 기존 압축 대상은 보존. # (응답 매처는 header <필드> <값> 한 쌍씩 — 여러 줄 = OR. 한 줄 다중 값은 파싱 에러) + # 2026-06-20 보안 헤더 (M: 클릭재킹·MIME 스니핑 방어). HSTS 는 TLS 종단 edge(home-caddy) 소관. + header { + X-Content-Type-Options nosniff + X-Frame-Options SAMEORIGIN + Referrer-Policy strict-origin-when-cross-origin + -Server + } encode { gzip match { diff --git a/app/api/documents.py b/app/api/documents.py index 9732489..d3af888 100644 --- a/app/api/documents.py +++ b/app/api/documents.py @@ -1277,6 +1277,14 @@ async def update_document( if val is not None and val not in ("business", "knowledge"): raise HTTPException(status_code=400, detail="doc_purpose는 business 또는 knowledge만 가능") + # edit_url SSRF 가드 (2026-06-20 M1): 내부/메타데이터 주소 후속 fetch 차단 (news.py 동형 검증) + if update_data.get("edit_url"): + from core.url_validator import validate_feed_url + try: + await asyncio.to_thread(validate_feed_url, update_data["edit_url"]) + except Exception as e: + raise HTTPException(status_code=400, detail=f"edit_url 검증 실패: {e}") + for field, value in update_data.items(): setattr(doc, field, value) doc.updated_at = datetime.now(timezone.utc) diff --git a/app/api/library.py b/app/api/library.py index a0137d7..26207ab 100644 --- a/app/api/library.py +++ b/app/api/library.py @@ -9,7 +9,7 @@ from sqlalchemy import func, select from sqlalchemy import text as sql_text from sqlalchemy.ext.asyncio import AsyncSession -from core.auth import get_current_user +from core.auth import get_current_user, require_admin from core.database import get_session from core.library import LIBRARY_PREFIX, MAX_DEPTH, normalize_library_path from models.category import LibraryCategory @@ -78,7 +78,7 @@ async def list_categories( @router.post("/categories", response_model=CategoryResponse, status_code=201) async def create_category( body: CategoryCreate, - user: Annotated[User, Depends(get_current_user)], + user: Annotated[User, Depends(require_admin)], session: Annotated[AsyncSession, Depends(get_session)], ): """카테고리 생성 (조상 자동 생성 포함)""" @@ -133,7 +133,7 @@ async def create_category( @router.patch("/categories", response_model=CategoryResponse) async def rename_category( body: CategoryRename, - user: Annotated[User, Depends(get_current_user)], + user: Annotated[User, Depends(require_admin)], session: Annotated[AsyncSession, Depends(get_session)], ): """카테고리 이름 변경 (leaf only, path 기반 식별)""" @@ -214,7 +214,7 @@ async def rename_category( @router.delete("/categories", status_code=204) async def delete_category( path: str = Query(..., description="삭제할 카테고리 경로"), - user: Annotated[User, Depends(get_current_user)] = None, + user: Annotated[User, Depends(require_admin)] = None, session: Annotated[AsyncSession, Depends(get_session)] = None, ): """카테고리 삭제 (leaf only, 문서 없는 경우만)""" @@ -410,7 +410,7 @@ async def get_facet_values( @router.post("/facets", response_model=FacetValueResponse, status_code=201) async def add_facet_value( body: FacetValueResponse, - user: Annotated[User, Depends(get_current_user)], + user: Annotated[User, Depends(require_admin)], session: Annotated[AsyncSession, Depends(get_session)], ): """facet 사전에 새 값 추가""" diff --git a/docker-compose.yml b/docker-compose.yml index 4e692db..dccdf1a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -263,7 +263,7 @@ services: caddy: image: caddy:2 ports: - - "8080:80" + - "127.0.0.1:8080:80" # 2026-06-20: LAN 우회 차단 (실 ingress=home-caddy→caddy:80 도커망) volumes: - ./Caddyfile:/etc/caddy/Caddyfile - caddy_data:/data diff --git a/scripts/audit_study_question_markdown.py b/scripts/audit_study_question_markdown.py index c4ffe04..58810ef 100644 --- a/scripts/audit_study_question_markdown.py +++ b/scripts/audit_study_question_markdown.py @@ -289,7 +289,7 @@ async def run(topic_id: int, exam_round: str, apply: bool, abort_threshold: int) host="postgres", port=5432, user="pkm", - password="uW38friypljVS0X2ULoMnw", + password=os.environ["POSTGRES_PASSWORD"], database="pkm", ) try: diff --git a/scripts/strip_outer_fences.py b/scripts/strip_outer_fences.py index 9d3da1a..12bf614 100644 --- a/scripts/strip_outer_fences.py +++ b/scripts/strip_outer_fences.py @@ -16,6 +16,7 @@ dry-run 먼저 출력 (각 필드 N건). 그 다음 --apply 옵션으로 UPDATE. from __future__ import annotations import asyncio +import os import re import sys @@ -99,7 +100,7 @@ async def main() -> None: host="postgres", port=5432, user="pkm", - password="uW38friypljVS0X2ULoMnw", + password=os.environ["POSTGRES_PASSWORD"], database="pkm", ) try: