Bỏ qua đến nội dung chính
CSPXSSsecurityHTTP headerweb security

Content Security Policy (CSP): Setup Đúng Cho Web 2026

Content Security Policy CSP setup từ A-Z: nonce, hash, strict-dynamic, report-only mode, common pitfall. Defense layer mạnh chống XSS.

Xuất bản 7 phút đọc

Content Security Policy là defense layer ngoài cùng chống XSS — block script không trust execute. Bài này code setup CSP đúng cho web 2026 với nonce + strict-dynamic.

CSP là gì?

HTTP header báo browser nguồn nào được phép load resource (script, style, image, font). Vi phạm = browser block + report violation.

Content-Security-Policy: default-src 'self';
  script-src 'self' 'nonce-abc123';
  style-src 'self' 'unsafe-inline';
  img-src 'self' https://cdn.alodev.vn data:;
  connect-src 'self' https://api.alodev.vn;
  frame-ancestors 'none';

Directive quan trọng

DirectiveControl
default-srcFallback cho directive khác
script-srcJS source — quan trọng nhất chống XSS
style-srcCSS source
img-srcImage source
connect-srcfetch/XHR/WebSocket destination
font-srcFont source
frame-ancestorsSite nào được embed (chống clickjacking)
base-uri<base> element value
form-actionForm submit destination

CSP cơ bản — không inline

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self';
  img-src 'self' data:;
  font-src 'self' data:;
  connect-src 'self';
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';

Strict — chỉ load từ same origin. App có inline script (Google Tag, hot reload Next dev) sẽ break. Cần nonce/hash.

Nonce — chuẩn modern

import crypto from 'node:crypto'

app.use((req, res, next) => {
  const nonce = crypto.randomBytes(16).toString('base64')
  res.locals.nonce = nonce
  res.setHeader('Content-Security-Policy',
    `default-src 'self'; ` +
    `script-src 'self' 'nonce-${nonce}' 'strict-dynamic'; ` +
    `style-src 'self' 'unsafe-inline'; ` +
    `img-src 'self' https: data:; ` +
    `connect-src 'self' https://api.alodev.vn;`
  )
  next()
})

// Render template với nonce
app.get('/', (req, res) => {
  res.send(`
    <script nonce="${res.locals.nonce}">
      console.log('Inline script trusted')
    </script>
  `)
})

Mỗi request nonce khác nhau → attacker không đoán được. Inline script có nonce match = trusted.

strict-dynamic

Cho phép trusted script (có nonce) tải script khác — không cần liệt kê origin từng CDN:

script-src 'nonce-abc' 'strict-dynamic';

Script với nonce có thể tải googletagmanager.com → Google tag tải analytics.com → cascade trusted. Reduce CSP verbose nhiều.

Hash — alternative cho inline static

Nếu inline script không đổi, hash thay nonce:

script-src 'self' 'sha256-pZmWyUR4kMVUVYAY8t2H+2UqBM+7Z3rnRxOpkhi1EOI=';

Browser SHA-256 nội dung script + verify match. Bypass cache không phá CSP.

Report mode — test trước khi enforce

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-abc'; report-uri /csp-report;

Browser KHÔNG block, chỉ POST violation tới /csp-report. Run 1-2 tuần, log mọi violation, fix, rồi switch sang enforce.

app.post('/csp-report', express.raw({ type: '*/*' }), (req, res) => {
  console.warn('CSP violation:', JSON.parse(req.body.toString()))
  res.status(204).end()
})

Next.js CSP với middleware

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(req: NextRequest) {
  const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
  const csp = `
    default-src 'self';
    script-src 'self' 'nonce-${nonce}' 'strict-dynamic';
    style-src 'self' 'unsafe-inline';
    img-src 'self' data: https://cdn.alodev.vn;
    connect-src 'self' https://api.alodev.vn;
    frame-ancestors 'none';
  `.replace(/\s{2,}/g, ' ').trim()

  const requestHeaders = new Headers(req.headers)
  requestHeaders.set('x-nonce', nonce)

  const res = NextResponse.next({ request: { headers: requestHeaders } })
  res.headers.set('Content-Security-Policy', csp)
  return res
}
// app/layout.tsx — đọc nonce từ header
import { headers } from 'next/headers'

export default async function Layout({ children }) {
  const nonce = (await headers()).get('x-nonce')
  return (
    <html>
      <body>
        <Script nonce={nonce} src="/analytics.js" />
        {children}
      </body>
    </html>
  )
}

Kết luận

Content Security Policy là defense XSS quan trọng — không phải optional 2026. Setup nonce + strict-dynamic + report-only mode 1 tuần là quy trình chuẩn. Combine với XSS prevention trong code và sanitize input để đạt defense-in-depth.

Zalo