CSP · Web 보안 · 원격 Mac · 2026

2026 원격 Mac 프론트엔드 피하기:
CSP nonce·strict-dynamic·Safari 오류 대조표 — 배포 전 3단계 검수

2026.04.08 Web·프론트 보안 약 7분 읽기

대상: CSP nonce·해시unsafe-inline을 걷어낸 Web 프론트 팀. Chromium만 QA 하다 Safari에서만 터지는 스크립트·스타일 차단을 줄이려면 WebKit 관점이 필요합니다. nonce/해시 대조, strict-dynamic 적용 순서, 원격 Mac Safari 검증, 배포 전 3단계 게이트와 FAQ를 한 페이지에 모았습니다. 배포 파이프라인은 Vite·Webpack + Safari 검증, 자동화는 Playwright WebKit 글과 이어서 읽으면 좋습니다.

01 CSP 기초와 Safari 차이

script-src에서 'unsafe-inline'을 빼면 인라인 스크립트마다 헤더와 일치하는 nonce 또는 본문 바이트가 맞는 SHA-256 해시가 필요합니다. Chromium DevTools의 위반 패널과 달리 Safari(WebKit)는 문구·필터 순서가 조금 다르고, 인라인 핸들러·javascript: URL·일부 워커에 더 빨리 걸리는 편이라 “크롬 통과 = 출하” 가정이 깨집니다.

프론트 팀이 놓치기 쉬운 축은 세 가지입니다. 동적 주입: GTM·지연 청크가 서버가 내려준 nonce를 물려받지 못하면 Safari에서 먼저 조용히 실패합니다. iframe: 결제·OAuth 임베드가 frame-src 한 줄에 막혀 UI는 멀쩡한데 네트워크만 막히는 패턴입니다. HTML 캐시: CDN·브라우저에 남은 이전 nonce HTML이면 간헐적 Refused to execute만 남습니다. nonce 회전에 맞춰 캐시 키를 설계하세요.

style-src도 동일한 훈련이 필요합니다. 위젯이 인라인 스타일을 박으면 nonce·해시 없이는 차단되며, 임시 예외를 둘 경우 보안 검토와 만료일을 명시하는 것이 운영에 유리합니다.

02 nonce·hash 설정 대조표

SSR·엣지에서 HTML을 깎는 구조라면 nonce가 기본값이고, 자주 바뀌지 않는 작은 인라인 스니펫(예: 고정 부트스트랩)은 해시가 맞을 때가 있습니다. 아래는 실무 의사결정용 요약입니다.

항목 Nonce SHA-256 해시
헤더 패턴 script-src 'nonce-<random>' …<script nonce="…">와 값 동일. script-src 'sha256-<base64>' — 인라인 바이트가 한 글자라도 다르면 실패.
적합한 경우 SSR 프레임워크, 요청별 HTML, strict-dynamic 체인의 루트. 고정 부트 한 줄, 팀이 소유한 소형 JSON-LD 등.
운영 리스크 오래된 HTML 캐시, 미들웨어에서 nonce 주입 누락. 포맷·미니파이 변경 시 해시 전부 재계산.
Safari 확인 포인트 하이드레이션·청크 로더마다 서버 셸의 nonce가 전달되는지. 개행·공백 변경 후에도 해시가 갱신됐는지.
실행 가능한 응답 헤더 예시 (요청마다 nonce)

예: res.locals.cspNonce에 암호학적 난수를 넣고 동일 값을 HTML 템플릿에 주입합니다.

const n = res.locals.cspNonce;
res.setHeader('Content-Security-Policy', [
  `default-src 'self'`,
  `script-src 'nonce-${n}' 'strict-dynamic' https:`,
  `style-src 'self' 'nonce-${n}'`,
  `object-src 'none'`,
  `base-uri 'none'`,
  `frame-ancestors 'none'`
].join('; '));

nonce 부트스트랩이 모든 하위 로드를 소유하면 https: 같은 넓은 토큰은 점진적으로 제거합니다. 원격 세션에서 Web Inspector를 다루는 팁은 Safari Web Inspector FAQ를 참고하세요.

03 strict-dynamic 적용 단계

'strict-dynamic'은 nonce로 신뢰된 스크립트가 로드하는 하위 스크립트를 호스트 목록 없이 허용하는 흐름을 만듭니다. 반대로 말하면 동적 삽입 경로에서는 예전처럼 script-src에 나열한 도메인만으로는 통과하지 못하는 경우가 많습니다.

  1. 루트 로더: 첫 동기 <script>에 nonce를 달고 그래프를 시작합니다.
  2. 지연 청크: 신뢰된 코드에서만 로드—nonce 없는 제3자 태그는 설계에서 제거합니다.
  3. 호스트 정리: 커버되면 script-src URL 나열을 줄이고, API·비콘은 connect-src를 정직하게 유지합니다.
  4. Report-Only: 프로덕션과 동일한 nonce 정책을 Content-Security-Policy-Report-Only로 먼저 맞춘 뒤 enforce로 전환합니다.
  5. 워커: 서비스 워커·공유 워커 스크립트가 정적 규칙 또는 nonce 규칙과 일치하는지 별도 확인합니다.

04 원격 Safari·WebKit 검증 흐름

원격 Mac은 사용자와 같은 Safari 빌드에서 HTTPS 스테이징을 열고, 개발 메뉴에서 Web Inspector를 붙이기 좋습니다. 사설 창은 확장 프로그램이 CSP를 건드리는지 배제하는 데 유리하고, 콘솔·네트워크에서 blocked:csp로 거부된 스크립트를 바로 볼 수 있습니다. 같은 호스트에서 Playwright webkit 스모크를 돌린 뒤, 결제·소셜 로그인처럼 서드파티가 많은 화면은 수동 Safari 패스를 남기세요.

Safari 콘솔 신호 추정 지시어 조치 방향
실행 거부… hash, nonce 또는 'unsafe-inline' 없음 script-src 헤더와 태그 nonce 일치·HTML 캐시 무효화.
로드 거부… CSP 위반 connect-src / frame-src API 호스트·SaaS iframe 출처 허용 목록 정리.
인라인 스타일이 CSP 위반 style-src 외부 CSS·해시·스타일 nonce를 동일 미들웨어에서 주입.

05 흔한 차단 FAQ

Chrome만 통과하고 Safari만 막히는데요?

WebKit이 인라인 핸들러·워커·일부 로더에 더 엄격한 경우가 많습니다. RC 서명은 실제 Safari로 하세요.

strict-dynamic 쓰면 호스트 화이트리스트가 의미 없나요?

동적 로드 경로에서는 호스트만으로는 부족하고 nonce 체인이 신뢰의 중심이 됩니다.

nonce를 여러 페이지에서 재사용해도 되나요?

안 됩니다. HTML 응답마다 새 nonce를 생성하고, 캐시된 페이지가 오래된 nonce를 들고 있지 않게 하세요.

06 배포 전 3단계 검수 체크리스트

원격 Mac의 실제 Safari에서 다음을 완료 조건으로 두면 프로덕션 CSP 사고를 줄일 수 있습니다.

  • 1단계 — 정책 동일성: 스테이징의 Content-Security-Policy(및 Report-Only)가 프로덕션과 바이트 단위로 같거나 의도된 차이만 문서화되어 있는지.
  • 2단계 — 콘솔 정리: 로그인·결제·대시보드·에러 페이지까지 미해결 CSP 오류가 없는지. 각 blocked:csp를 지시어에 매핑했는지.
  • 3단계 — 증빙: Safari·macOS 버전 문자열과 스크린샷 또는 HAR을 릴리스 티켓에 첨부하고, 의존성 업그레이드 후 재실행.
한 줄 요약

strict-dynamic의 첫 스크립트는 팀이 신뢰하는 부트스트랩이어야 하고, HTML은 nonce 회전에 맞는 캐시 정책을 타야 합니다. 그 다음에야 “Chromium 그린”이 의미 있습니다.

Safari CSP 실증

원격 Mac으로 진짜 Safari에서 CSP 서명하기

프로덕션과 동일한 CSP를 대여 노드에 올려 WebKit 위반을 재현·제거하고, 위 3단계 게이트를 통과한 뒤 출하하세요. 요금·구매·예약·도움말로그인 없이 열어볼 수 있습니다.

nonce·strict-dynamic 네이티브 WebKit Apple Silicon

· 구매 · 요금 · 도움말 · 블로그 · 배포 전 Web 스모크

M4 대여 — Safari CSP 검증