CSP 헤더 생성기
지시문, 해시, nonce로부터 Content-Security-Policy 헤더 구축. 엄격 모드 템플릿과 Report-Only 디버그 모드, Express / Nginx 스니펫 지원. 브라우저에서 처리.
- 브라우저에서 처리
- 데이터가 브라우저 밖으로 나가지 않습니다
- 무료 · 회원가입 불필요
WeChat으로 스캔하여 공유
예시·자세한 설명·자주 묻는 질문 실제 출력이 있는 예시, 다른 도구와의 차이, 자주 묻는 질문.
Strict CSP 시작 구성
Strict 프리셋은 web.dev에서 설명하는 nonce 기반 strict CSP를 따릅니다. HTTP 헤더 탭의 출력은 다음과 같습니다.
Content-Security-Policy: default-src 'self'; script-src 'nonce-{RANDOM}' 'strict-dynamic'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests
'nonce-{RANDOM}'는 자리표시자이며 그대로는 쓸 수 없습니다. 서버가 응답마다 128비트 이상의 새 무작위 값으로 바꾸고, 렌더링하는 각 <script>의 nonce=”…”에 같은 값을 써야 합니다. 정책에 자리표시자가 남아 있는 동안 도구는 경고를 표시합니다. 고정된 nonce 하나를 정적 설정에 붙여 넣지 마세요. 한 번 본 사람은 누구나 주입한 스크립트에서 다시 쓸 수 있습니다.
이전 버전의 이 프리셋은 ‘self’를 썼지만 자리표시자로 바꿨습니다. ‘strict-dynamic’이 있으면 CSP Level 3은 일치하는 nonce나 해시가 없는 한 HTML 파서가 삽입한 스크립트를 모두 차단하고, ‘self’와 호스트 소스는 더 이상 확인하지 않습니다(CSP3 §8.2). 그래서 script-src ‘self’ ‘strict-dynamic’만으로는 현재 Chrome, Firefox, Safari에서 스크립트가 하나도 실행되지 않으며, 도구는 이제 이 조합에 경고를 띄웁니다. nonce를 설정할 수 없는 정적 빌드 사이트라면 자리표시자를 지우고 해시 계산기로 인라인 스크립트마다 해시를 추가하거나, ‘중간’ 프리셋에서 시작하세요.
Express (helmet) 탭은 응답마다 값을 만드는 부분까지 출력합니다. crypto.randomBytes(16).toString(‘base64’)를 res.locals.cspNonce에 저장하는 미들웨어를 추가하고 script-src에 함수를 넘기는데, helmet README에 나오는 방식입니다. 템플릿에서는 이 값을 각 script 태그에 출력해야 합니다. Nginx 탭은 자리표시자를 그대로 두고 주석을 붙입니다. Nginx는 애플리케이션이 렌더링하는 HTML에 같은 nonce를 넣을 수 없기 때문입니다.
흔한 함정
’none’은 단독으로만 효과가 있습니다. 다른 소스와 함께 적으면‘none’은 효과가 없고 다른 소스는 허용됩니다.- HTTP 헤더 전용 지시문이 있습니다. CSP3 §3.3에 따라
<meta>안의frame-ancestors,report-uri,sandbox는 무시되고 Report-Only 모드도 없습니다. 도구는<meta>탭에서 이들을 빼고 경고합니다. ‘strict-dynamic’에는 nonce나 해시가 필요합니다. 없으면 페이지의 스크립트가 모두 차단되며, 도구가 이를 알려 줍니다.- 해시/nonce가 있으면
‘unsafe-inline’은 무시됩니다. 최신 브라우저는 더 안전한 쪽을 우선합니다. default-src는 반드시 정의하세요. 누락된 fetch 지시문은default-src로 폴백하며, 이것이 없으면 새로 추가될 지시문에도 폴백이 적용되지 않습니다.- 커스텀 호스트에 세미콜론을 넣지 마세요. 세미콜론은 정책을 끊어 이후 모든 지시문을 손상시킵니다.
해시 vs. Nonce
정적 인라인 콘텐츠(애널리틱스 스니펫, 크리티컬 CSS, 서버 렌더링되는 보조 스크립트)에는 해시를 사용합니다. 정적 HTML과 엣지 캐시와 잘 맞고, 요청별 값을 필요로 하지 않습니다.
요청마다 새로 렌더링되는 콘텐츠에는 nonce를 사용합니다. 서버가 헤더에 ‘nonce-XYZ’를 넣고, 동일한 값을 모든 <script nonce=“XYZ”>에 새깁니다. ‘strict-dynamic’과 함께 사용하면 신뢰된 스크립트의 자식 스크립트도 신뢰가 상속돼 거대한 호스트 허용 목록을 유지할 필요가 없습니다.
배포 치트시트
- Nginx: Nginx 탭 내용을
server블록에 붙여넣고 reload.always플래그는 오류 응답에도 헤더를 강제합니다. - Apache:
.htaccess나httpd.conf에Header always set Content-Security-Policy ”…”를 작성합니다. - Express / Node: helmet 스니펫을 붙여넣고,
useDefaults를false로 설정해 정책이 helmet 기본값과 조용히 병합되지 않도록 하세요. - Cloudflare Pages / Vercel / Netlify: HTTP 헤더 형식을 각 플랫폼의 헤더 설정(
_headers,vercel.json,netlify.toml)에 붙여넣습니다.
FAQ
Content Security Policy란 무엇인가요?
CSP는 브라우저에 스크립트, 스타일, 이미지, 폰트, 프레임 등 신뢰할 수 있는 콘텐츠 출처를 알려주는 HTTP 응답 헤더입니다. 출력 인코딩과 함께 사용하면 XSS, 클릭재킹, 데이터 인젝션 공격에 대한 가장 강력한 브라우저 측 방어선이 됩니다.
Enforce와 Report-Only 중 무엇을 먼저 배포해야 하나요?
항상 `Content-Security-Policy-Report-Only`와 `report-uri`(또는 `report-to`) 엔드포인트로 시작하세요. 1~2주 동안 실제 트래픽의 위반 사례를 수집해 잘못된 부분을 코드에서 수정한 다음, 강제형 `Content-Security-Policy`로 전환합니다.
인라인 스크립트에 'unsafe-inline'이 꼭 필요한가요?
가능한 한 피하세요. 최신 CSP는 콘텐츠 기반 해시(`'sha256-...'`, `'sha384-...'`, `'sha512-...'`)와 요청별 nonce(`'nonce-...'`)를 지원합니다. 이 페이지의 해시 계산기로 특정 스니펫에 맞는 해시를 즉시 만들 수 있습니다.
'strict-dynamic'은 어떤 역할을 하나요?
nonce나 해시로 신뢰된 스크립트가 런타임에 다른 스크립트를 모든 CDN 호스트를 나열하지 않고도 로드할 수 있게 합니다. 모던 앱에서 권장되는 패턴이며, 자식 스크립트에 대해 호스트 기반 허용 목록을 무효화하고 일반적인 우회를 차단합니다.
생성된 헤더는 어디에 배포해야 하나요?
원본 서버에서 `Content-Security-Policy`를 전송하세요(Nginx `add_header`, Apache `Header set`, Cloudflare Transform Rules, Vercel `headers` 등). HTML `<meta http-equiv>` 형태는 정적 호스트용 대안입니다. CSP Level 3에서는 `<meta>` 안의 `frame-ancestors`, `report-uri`, `sandbox`가 무시되므로 도구는 그 탭에서 이들을 뺍니다. `report-to`는 `<meta>`에서도 동작하지만 엔드포인트는 `Reporting-Endpoints` 헤더로 정의합니다.