웹 개발
배포 직후 CSP가 인라인 스크립트를 막았을 때 고른 수정 기준
보안 헤더를 추가한 직후 관리자 화면이 멈춘 사례를 바탕으로, CSP를 느슨하게 풀지 않고 원인을 좁히는 방법입니다.
화면은 열리는데 버튼만 움직이지 않았다
Nginx에 Content-Security-Policy 헤더를 넣은 다음 배포에서 관리자 화면의 메뉴와 로그인 버튼이 조용히 멈췄다. 네트워크 탭은 200이어서 API 문제로 보기 쉬웠다. 브라우저 콘솔에는 인라인 스크립트 실행이 차단됐다는 메시지가 있었다.
처음에는 unsafe-inline을 추가해서 급히 되돌릴 뻔했다. 하지만 CSP를 넣은 목적을 스스로 지우는 선택이라, 어떤 스크립트가 왜 인라인인지부터 확인했다.
먼저 보고 있는 헤더를 확인한다
프록시, CDN, 애플리케이션이 모두 헤더를 붙이면 예상과 다른 CSP가 최종 응답에 실릴 수 있다.
curl -sI https://example.com/admin | tr -d '
' | grep -i '^content-security-policy:'
개발자 도구의 Console에서는 차단된 리소스와 directive를 확인했다. Next.js가 생성하는 스크립트와 내가 넣은 분석 스니펫은 원인이 다르므로 한 덩어리로 예외 처리하지 않았다.
내가 적용한 수정 순서
정적 외부 스크립트는 가능한 한 자체 호스팅하거나 명시한 도메인만 script-src에 추가했다. 직접 작성한 작은 초기화 코드는 별도 파일로 옮겼다. 요청마다 필요한 인라인 코드라면 nonce를 서버에서 만들고, 해당 응답의 CSP와 script 태그에 같은 nonce를 넣는 방식을 검토한다.
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://analytics.example; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
이 예시는 출발점일 뿐이다. 이미지 CDN, 폰트, API 도메인은 실제 브라우저 차단 기록을 보고 최소한으로 추가해야 한다. report-only 헤더로 먼저 관찰하는 배포도 안전했다.
설정을 바꿀 때 함께 본 항목
- HTTP와 HTTPS 응답의 CSP가 같은지 확인한다.
- 오류 화면과 관리자 화면을 모두 새로고침한다.
- nonce나 해시를 쓴다면 캐시가 다른 응답의 값을 섞지 않는지 본다.
unsafe-eval,unsafe-inline을 임시 해결책으로 남기지 않는다.
이번 경우의 결론은 간단했다. 보안 헤더 오류를 기능 오류처럼 넓게 고치지 말고, 브라우저가 알려 준 directive와 스크립트 출처를 한 쌍으로 확인하는 편이 훨씬 덜 위험했다.