← 전체 글로 돌아가기

웹 개발

배포 직후 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 헤더로 먼저 관찰하는 배포도 안전했다.

설정을 바꿀 때 함께 본 항목

  1. HTTP와 HTTPS 응답의 CSP가 같은지 확인한다.
  2. 오류 화면과 관리자 화면을 모두 새로고침한다.
  3. nonce나 해시를 쓴다면 캐시가 다른 응답의 값을 섞지 않는지 본다.
  4. unsafe-eval, unsafe-inline을 임시 해결책으로 남기지 않는다.

이번 경우의 결론은 간단했다. 보안 헤더 오류를 기능 오류처럼 넓게 고치지 말고, 브라우저가 알려 준 directive와 스크립트 출처를 한 쌍으로 확인하는 편이 훨씬 덜 위험했다.