보안 강화: CORS 제한, Path Traversal 방어, 헬스체크 추가

- travel-proxy: get_thumb NameError 수정 및 경로 조작 방어
- stock-lab, music-lab: CORS allow_origins=* → 환경변수 기반 도메인 제한
- travel-proxy, deployer: /health 엔드포인트 추가
- 전 서비스 .dockerignore 추가 (.git, __pycache__, .env 제외)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 01:08:39 +09:00
parent c9737b380f
commit bc9ba3901e
9 changed files with 57 additions and 10 deletions

6
backend/.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
.git
__pycache__
*.pyc
.env
.env.*
*.md

6
deployer/.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
.git
__pycache__
*.pyc
.env
.env.*
*.md

View File

@@ -32,6 +32,10 @@ def run_deploy_script():
except Exception as e:
logger.exception(f"Exception during deployment: {e}")
@app.get("/health")
def health():
return {"status": "healthy", "service": "deployer"}
@app.post("/webhook")
async def webhook(req: Request, background_tasks: BackgroundTasks):
body = await req.body()

6
music-lab/.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
.git
__pycache__
*.pyc
.env
.env.*
*.md

View File

@@ -16,11 +16,13 @@ from .db import (
app = FastAPI()
_cors_origins = os.getenv("CORS_ALLOW_ORIGINS", "http://localhost:3007,http://localhost:8080").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
allow_origins=[o.strip() for o in _cors_origins],
allow_credentials=False,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Content-Type"],
)
MUSIC_AI_SERVER_URL = os.getenv("MUSIC_AI_SERVER_URL", "")

6
stock-lab/.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
.git
__pycache__
*.pyc
.env
.env.*
*.md

View File

@@ -23,12 +23,13 @@ from .price_fetcher import get_current_prices
app = FastAPI()
# CORS 설정 (프론트엔드 접근 허용)
_cors_origins = os.getenv("CORS_ALLOW_ORIGINS", "http://localhost:3007,http://localhost:8080").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 운영 시에는 구체적인 도메인으로 제한하는 것이 좋음
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
allow_origins=[o.strip() for o in _cors_origins],
allow_credentials=False,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Content-Type"],
)
scheduler = BackgroundScheduler(timezone=os.getenv("TZ", "Asia/Seoul"))

View File

@@ -0,0 +1,6 @@
.git
__pycache__
*.pyc
.env
.env.*
*.md

View File

@@ -168,6 +168,10 @@ def scan_album(album: str) -> List[Dict[str, Any]]:
# -----------------------------
# Routes
# -----------------------------
@app.get("/health")
def health():
return {"status": "healthy", "service": "travel-proxy"}
@app.get("/api/travel/regions")
def regions():
_meta_changed_invalidate_cache()
@@ -239,12 +243,18 @@ def photos(
@app.get("/media/travel/.thumb/{album}/{filename}")
def get_thumb(album: str, filename: str):
if ".." in album or ".." in filename:
raise HTTPException(400, "Invalid path")
src = (ROOT / album / filename).resolve()
if not str(src).startswith(str(ROOT)):
raise HTTPException(403, "Access denied")
if not src.exists() or not src.is_file():
raise HTTPException(404, "Source not found")
p = ensure_thumb(src, album)
if not p.exists() or not p.is_file():
raise HTTPException(404, "Thumbnail not found")
# src로부터 thumb 생성/확인 (원본 확장자 유지)
p = ensure_thumb(src, album)
return FileResponse(str(p))
@app.get("/api/version")