Files
2nd/10_Wiki/Topics/Domain_Programming/Coding/Security_CSP_Headers.md
T
Antigravity Agent c24165b8bc refactor(topics): 멀티 에이전트용 지식 재편 — _Common(공통 기본기) + Domain_* 구조
에이전트 8종(대화형/프로그래머 C·S/디자이너/설계자/기획자/QA/PD/PM)에게
[공통 기본 능력 + 롤별 Specialty] 2층으로 지식을 주입하기 위한 재분류.
문서 내용·포맷은 무수정, 폴더 이동만 (6,372개 문서 수 보존 확인).

- Topic_Programming → Domain_Programming (내부 구조 보존)
- Topic_Graphic → Domain_Design
- Topic_Business → Domain_Product
- Topic_General → Domain_General
- _Common 신설: Math(구 Topic_Math_Specialty), Reasoning(구 General/From_Thinking & Reasoning),
  Reasoning_Creativity(구 General/From_창의성), Communication(Poetic_Blog_Writing + From_writing)
- 타 도메인의 From_* 폴더는 유지 (출처 표기일 뿐, 이미 도메인에 맞게 분류된 문서)
- 빈 폴더 정리 (memory/procedures)
- 에이전트→폴더 매핑은 workspace의 .astra/agent-knowledge-map.json (9개 에이전트)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:05:56 +09:00

5.0 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
security-csp-headers CSP / 보안 헤더 — XSS / Clickjacking 방어 Coding draft B conceptual 2026-05-09 2026-05-09
security
csp
headers
web
vibe-coding
language applicable_to
TS / HTTP
Frontend
Backend
CSP
Content-Security-Policy
nonce
HSTS
X-Frame-Options
helmet

CSP / 보안 헤더

XSS 방어 마지막 보호막 = Content-Security-Policy. 인라인 script 차단 + nonce. HSTS / X-Frame-Options / Permissions-Policy 같이 묶어서 helmet 으로.

📖 핵심 개념

  • CSP: 어떤 origin 의 자원만 로드 가능한지 명시.
  • nonce: 매 요청마다 random — 인라인 script 1회 통과.
  • Strict CSP: 'strict-dynamic' + nonce — host allowlist 안 필요.
  • Report-only: 위반 보고만, 차단 X (점진 도입).

💻 코드 패턴

Helmet (Express)

import helmet from 'helmet';

app.use(helmet({
  contentSecurityPolicy: {
    useDefaults: false,
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", `'nonce-${res.locals.cspNonce}'`, "'strict-dynamic'"],
      styleSrc: ["'self'", "'unsafe-inline'"], // CSS 는 보통 강 X
      imgSrc: ["'self'", 'data:', 'https:'],
      connectSrc: ["'self'", 'https://api.example.com'],
      fontSrc: ["'self'", 'https://fonts.gstatic.com'],
      objectSrc: ["'none'"],
      frameAncestors: ["'none'"],
      baseUri: ["'self'"],
      formAction: ["'self'"],
    },
  },
  crossOriginOpenerPolicy: { policy: 'same-origin' },
  crossOriginResourcePolicy: { policy: 'same-origin' },
  hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
}));

nonce 발급

import { randomBytes } from 'node:crypto';

app.use((req, res, next) => {
  res.locals.cspNonce = randomBytes(16).toString('base64');
  next();
});

// 템플릿
res.render('index', { nonce: res.locals.cspNonce });
<script nonce="<%= nonce %>">
  // OK
</script>

Next.js (next.config.js)

const cspHeader = `
  default-src 'self';
  script-src 'self' 'nonce-NONCE' 'strict-dynamic';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  font-src 'self';
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';
`.replace(/\s{2,}/g, ' ').trim();

module.exports = {
  async headers() {
    return [{
      source: '/:path*',
      headers: [
        { key: 'Content-Security-Policy', value: cspHeader },
        { key: 'Strict-Transport-Security', value: 'max-age=31536000; includeSubDomains; preload' },
        { key: 'X-Content-Type-Options', value: 'nosniff' },
        { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
        { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
        { key: 'X-Frame-Options', value: 'DENY' },
      ],
    }];
  },
};

CSP report-only (점진)

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
app.post('/csp-report', express.json({ type: 'application/csp-report' }), (req, res) => {
  log.warn('csp violation', req.body['csp-report']);
  res.status(204).end();
});

Permissions-Policy (sensors / 권한)

Permissions-Policy: camera=(), microphone=(), geolocation=(self), payment=()

HSTS (HTTPS 강제)

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

preload: hstspreload.org 등록 시 브라우저 hardcoded list 에 포함.

Subresource Integrity (CDN script)

<script src="https://cdn.example.com/lib.js"
        integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
        crossorigin="anonymous"></script>

CDN 가 변조되면 브라우저가 차단.

X-Frame-Options vs frame-ancestors

  • X-Frame-Options: DENY (legacy)
  • CSP frame-ancestors 'none' (modern, 우선)

🤔 의사결정 기준

헤더 우선
모든 사이트 HSTS + X-Content-Type-Options + Referrer-Policy + Permissions-Policy
XSS 방어 강 CSP strict + nonce
Iframe 사용 안 함 frame-ancestors 'none'
외부 cdn 의존 SRI integrity
SaaS 임베드 가능 필요 frame-ancestors specific origin
점진 도입 Report-only → enforce

안티패턴

  • 'unsafe-inline' 그대로: CSP 의미 절반 잃음. nonce.
  • * 허용: CSP 무의미.
  • Report 검토 안 함: 위반 모름.
  • HSTS 없음 (HTTP 가능): SSL strip.
  • Self-signed cert prod: HSTS 못 씀.
  • X-XSS-Protection 1: 옛 헤더, modern X. 빼기.
  • Helmet 끄기 — 빠르다고: 기본은 거의 무료.
  • Hash 만 + dynamic script: 매 변경 hash 갱신 필요.

🤖 LLM 활용 힌트

  • Helmet defaults 그대로 좋다.
  • CSP = nonce + strict-dynamic.
  • Report-only 로 시작 → enforce.

🔗 관련 문서