보안 점검과 PageSpeed 성능 개선

오늘의 목표와 맥락

운영 중인 블로그 서비스에 대해 두 가지를 진행했다. 하나는 백엔드 전반의 보안 점검이고, 다른 하나는 PageSpeed 측정에서 지적된 성능 문제(캐시 수명 미설정 1,020KiB, 렌더링 차단 CSS 2,250ms, 미사용 JS 259KiB, llms.txt 형식 위반)의 해결이다.

측정 결과를 뜯어 보니 원인은 배포 구조에 있었다. Nginx가 프론트 빌드 산출물을 정적 서빙하는데 /assets 경로에 캐시 헤더가 전혀 없었고, Ubuntu 기본 Nginx는 HTML만 gzip 압축하므로 JS와 CSS가 원본 크기 그대로 전송되고 있었다. llms.txt는 저장소에 아예 없어서 SPA fallback으로 index.html이 대신 응답되어 "형식 위반"으로 잡힌 것이었다.

구현한 내용

성능 개선

보안 강화 (TDD, Red → Green 순서)

설계와 판단

보안 점검에서 인젝션, IDOR, 관리자 권한 체크, 저장형 XSS(서버 nh3와 프론트 DOMPurify 이중 방어)는 문제 없음을 확인했고, 실제 조치가 필요한 항목만 골라 우선순위를 매겼다. 가장 시급한 것은 토큰의 쿼리스트링 노출과 SECRET_KEY 폴백 두 가지로 판단해 이번에 처리했다.

폰트는 unicode-range 서브셋 방식이라 로딩 자체는 정상이었다. 문제는 폰트 파일이 아니라 선언 CSS의 크기였고, font-display: swap 덕분에 선언을 비동기로 빼도 폴백 글꼴로 즉시 렌더되므로 체감 저하 없이 차단만 제거할 수 있다고 판단했다.

네트워크 종속 트리(HTML → JS → API 체인)는 CSR SPA의 구조적 특성이라 SSR 도입 없이는 근본 해결이 어렵고, 현재 규모 대비 비용이 커서 이번 범위에서 제외했다.

문제와 해결

PR 기반 흐름을 쓰려 했으나 gh CLI가 저장소 소유자와 다른 계정으로 로그인되어 있어 비공개 저장소에 PR을 만들 수 없었다. CI와 동일한 검사(pytest, ruff, eslint, 빌드)를 로컬에서 전부 통과시킨 뒤 로컬 스쿼시 머지로 main에 반영하는 방식으로 대응했고, main 푸시 시점에 CI가 자동 실행되므로 검증 공백은 없다.

작업 트리에 있던 별도 진행 중 변경(테스트 헬퍼의 cli_port 옵션)은 stash로 분리해 보안 커밋과 섞이지 않게 했다가 작업 후 복원했다.

검증한 내용