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
| Directive | Control |
|---|---|
| default-src | Fallback cho directive khác |
| script-src | JS source — quan trọng nhất chống XSS |
| style-src | CSS source |
| img-src | Image source |
| connect-src | fetch/XHR/WebSocket destination |
| font-src | Font source |
| frame-ancestors | Site nào được embed (chống clickjacking) |
| base-uri | <base> element value |
| form-action | Form 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.