
CSP(Content-Security-Policy)는 "이 페이지는 어떤 출처의 스크립트·스타일·이미지만 실행·표시한다"를 브라우저에 알려 주는 응답 헤더입니다. 제대로 걸리면 XSS 로 주입된 스크립트가 실행 자체가 막히므로 가장 강력한 방어선이 됩니다. 문제는 처음부터 강하게 걸면 사이트가 바로 깨진다는 것. 그래서 보고 모드로 시작해 단계별로 조이는 순서가 정석입니다.

1단계 — 보고 모드로 현황 보기
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
Report-Only 는 아무것도 막지 않고 위반만 보고합니다. 브라우저 콘솔에도 뜨고, /csp-report 로 JSON 이 POST 됩니다. 이 보고를 일주일쯤 모으면 사이트가 실제로 어떤 출처를 쓰는지 목록이 나옵니다 — 생각보다 많습니다(분석 도구, 글꼴, 지도, 위젯).
2단계 — 지시어별로 허용 목록
default-src 'self';
script-src 'self' https://www.googletagmanager.com;
style-src 'self' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https:;
connect-src 'self' https://api.example.com;
frame-ancestors 'none';

외부 출처는 https://*.example.com 같은 와일드카드보다 정확한 호스트를 적습니다. img-src 의 data: 는 인라인 이미지를 위해 흔히 허용하지만, script-src 에 data: 나 'unsafe-eval' 을 넣으면 CSP 의 의미가 거의 사라집니다.
3단계 — 인라인 스크립트를 nonce 로
가장 많이 걸리는 위반은 HTML 안의 <script>…</script> 와 onclick="" 같은 인라인 코드입니다. 'unsafe-inline' 을 허용하면 편하지만 그 순간 XSS 방어는 없어집니다. 대신 요청마다 임의의 nonce 를 만들어 헤더와 스크립트 태그에 함께 넣습니다.
Content-Security-Policy: script-src 'self' 'nonce-r4nd0m123'
<script nonce="r4nd0m123"> … </script>
nonce 는 요청마다 새로 만들고 예측 불가능해야 합니다. 템플릿 엔진에 변수로 넘기면 됩니다. 인라인 이벤트 핸들러(onclick)는 nonce 를 붙일 수 없으니 addEventListener 로 옮깁니다.
4단계 — 강제 모드로
헤더 이름을 Content-Security-Policy 로 바꾸되, 위반 보고는 계속 받습니다. 새 기능이 외부 스크립트를 추가하면 보고로 먼저 알게 됩니다.

자주 하는 실수
- 프런트엔드 빌드 도구가 넣는 인라인 스타일 때문에
style-src 'unsafe-inline'이 필요한 경우가 많습니다. 스타일은 스크립트보다 위험이 낮아 허용하는 타협이 흔합니다. - CSP 는 응답 헤더로 넣는 것이 정석입니다.
<meta>태그로도 되지만 frame-ancestors·report-uri 는 meta 에서 동작하지 않습니다. - 헤더가 두 곳(프록시와 앱)에서 나가면 둘 다 적용되어 더 엄격해집니다. 한 곳에서만 내보내세요.
CSP 는 한 번에 완성하는 것이 아니라 보고를 보며 조여 가는 것입니다. 보고 모드만 켜도 사이트가 무엇을 쓰고 있는지 알게 되는 것 자체가 큰 소득입니다.
댓글