Content Security Policy trong Next.js: Chặn script lạ bằng CSP Header
Thiết lập CSP Header an toàn cho Next.js App Router: directives quan trọng, nonce động, strict-dynamic, third-party scripts, reporting, kiểm thử và checklist production.
ShanverseCập nhật 8 phút đọc— lượt xemĐang tải lượt xem
Next.js
Web Security
CSP giải quyết vấn đề gì?
Khi ứng dụng bị chèn một thẻ script, event handler như onclick, URL javascript: hoặc lời gọi eval(), trình duyệt thường không biết mã nào thuộc ứng dụng và mã nào do kẻ tấn công đưa vào. Header Content-Security-Policy cung cấp một tập chỉ thị để giới hạn nguồn script, style, ảnh, font, kết nối mạng, frame và nhiều loại tài nguyên khác.
Ví dụ tối thiểu:
Policy này chỉ cho phép script cùng origin, cấm plugin qua object, ngăn thay đổi URL nền bằng thẻ base và không cho trang bị nhúng vào frame. Tuy nhiên, allowlist
Bình luận
'self'
chưa phải strict CSP: nếu origin của bạn có JSONP, file upload hoặc endpoint trả nội dung do người dùng kiểm soát, kẻ tấn công vẫn có thể tìm đường tải script từ chính origin đó.
Những directive quan trọng
default-src: fallback cho các loại tài nguyên chưa có directive riêng.
script-src: kiểm soát JavaScript, inline script, nonce, hash và eval().
style-src: kiểm soát stylesheet và inline style.
img-src, font-src, media-src: giới hạn ảnh, font và media.
connect-src: giới hạn fetch, XHR, WebSocket và EventSource.
frame-src: kiểm soát frame mà trang được phép tải.
object-src 'none': vô hiệu hóa plugin object/embed cũ.
base-uri 'self' hoặc 'none': ngăn kẻ tấn công đổi base URL.
form-action 'self': giới hạn đích gửi form.
upgrade-insecure-requests: nâng HTTP subresource lên HTTPS.
Hãy bắt đầu với default-src 'self', sau đó khai báo rõ từng loại cần thiết. Không mở rộng *, https: hoặc cả một CDN nếu chỉ cần một origin cụ thể.
Static CSP qua next.config.ts
Nếu ứng dụng không có inline script cần nonce, bạn có thể đặt một policy cố định bằng headers():
Cách này dễ cache và phù hợp với policy dựa trên nguồn. Nhưng một ứng dụng Next.js có thể cần inline bootstrap script. Đừng thêm 'unsafe-inline' chỉ để hết lỗi console; điều đó làm suy yếu mục tiêu quan trọng nhất của CSP.
Strict CSP với nonce theo từng request
Nonce là chuỗi ngẫu nhiên, chỉ dùng một lần cho một response. script-src cho phép script mang nonce đúng chạy, còn script bị chèn không biết nonce sẽ bị chặn.
Trong các phiên bản Next.js mới, logic đứng trước route có thể nằm trong proxy.ts; dự án cũ hơn có thể dùng middleware.ts. Nguyên tắc không đổi: tạo nonce mới cho từng request, đưa nonce vào CSP request header để Next.js đọc khi render, đồng thời đặt CSP vào response.
Nonce phải có entropy đủ mạnh và không được tái sử dụng giữa các response. Không lấy nonce từ input của người dùng. Không coi nonce là secret lâu dài; giá trị của nó xuất hiện trong HTML, nhưng kẻ tấn công không được có khả năng đoán trước nonce của response đang bị tấn công.
Dùng nonce trong App Router
Server Component có thể đọc nonce từ request header và truyền vào component hoặc next/script:
Chỉ thêm origin bên thứ ba vào policy khi thực sự cần. Một analytics script có thể tiếp tục tải worker, ảnh tracking hoặc gọi API, vì vậy bạn có thể phải cấu hình thêm connect-src, img-src, worker-src hay frame-src. Mở đúng capability, không mở toàn bộ Internet.
'strict-dynamic' hoạt động thế nào?
Khi trình duyệt hỗ trợ CSP3, 'strict-dynamic' chuyển niềm tin từ allowlist host sang script đã được xác thực bằng nonce hoặc hash. Một script tin cậy có thể tải dependency của nó. Cách này thường bền vững hơn danh sách hostname dài, nhưng cũng có nghĩa script được nonce có quyền tải thêm script: chỉ nonce cho code bạn thực sự tin cậy.
Đừng nhầm nonce với hostname allowlist. Với strict CSP, nonce hoặc hash là gốc tin cậy. Hạn chế nhúng script bên thứ ba vì nếu nhà cung cấp bị compromise, CSP vẫn xem script có nonce là hợp lệ.
Development khác production
Next.js development có thể cần 'unsafe-eval' cho source maps hoặc tooling. Chỉ thêm nó khi NODE_ENV === 'development'. Production không nên có:
'unsafe-inline' trong script-src.
'unsafe-eval' nếu ứng dụng không thực sự cần.
Wildcard *.
Scheme rộng như https: thay vì origin cụ thể.
Nonce cố định trong biến môi trường.
Inline style là bài toán riêng. Nếu framework hoặc thư viện cần style inline, ưu tiên nonce/hash hoặc CSS file. Không suy luận rằng vì style ít nguy hiểm hơn script nên có thể mở policy vô hạn.
Triển khai an toàn với Report-Only
Policy mới có thể làm hỏng production nếu chặn chunk, API, ảnh hoặc dịch vụ thanh toán. Triển khai theo ba bước:
Gửi Content-Security-Policy-Report-Only để thu thập vi phạm mà chưa chặn.
Chuẩn hóa report, loại nhiễu từ extension trình duyệt và kiểm tra các flow quan trọng.
Chuyển sang Content-Security-Policy enforcement khi policy ổn định.
Có thể cấu hình reporting bằng report-to hoặc report-uri tùy mức hỗ trợ và hệ thống thu thập. Report có thể chứa URL nhạy cảm, vì vậy cần kiểm soát retention, quyền truy cập và dữ liệu cá nhân.
CORS, SRI và Trusted Types không phải CSP
CORS kiểm soát việc một origin khác đọc response; nó không quyết định script nào được chạy trên trang.
Subresource Integrity xác minh nội dung file bên thứ ba bằng hash; hữu ích khi URL file cố định nhưng không thay thế CSP.
Trusted Types giảm DOM XSS bằng cách kiểm soát dữ liệu đi vào các sink nguy hiểm. Có thể kết hợp require-trusted-types-for 'script' sau khi codebase tương thích.
Sanitization vẫn bắt buộc khi render HTML không tin cậy. CSP không biến dangerouslySetInnerHTML thành an toàn.
Kiểm thử CSP
Kiểm tra header bằng DevTools Network và curl -I.
Chạy các trang SSR, route động, error page, login, checkout và callback OAuth.
Xác nhận mọi response HTML đều có policy, không chỉ trang chủ.
Thử chèn một inline script không nonce; nó phải bị chặn.
Kiểm tra script có nonce đúng vẫn chạy.
Kiểm tra build production; behavior dev không đại diện cho production.
Theo dõi violation report theo directive, route, browser và release.
Dùng CSP Evaluator hoặc scanner tương đương để phát hiện policy yếu.
Checklist production
Policy bắt đầu từ default-src 'self' và khai báo capability tối thiểu.
object-src 'none', base-uri và frame-ancestors được đặt rõ.
Không có 'unsafe-inline' trong script-src production.
Không có 'unsafe-eval' ngoài development nếu không thật sự bắt buộc.
Nonce được tạo bằng CSPRNG mới cho từng request và không cache nhầm HTML giữa người dùng.
Third-party origins được rà soát theo từng directive.
CSP được thử trước bằng Report-Only và có hệ thống giám sát violation.
HTML không tin cậy vẫn được sanitize; unsafe sinks đã được loại bỏ hoặc bao bọc.
Policy được kiểm thử lại sau mỗi thay đổi framework, analytics, CDN hoặc payment SDK.
Kết luận
CSP hiệu quả không phải là danh sách hostname càng dài càng tốt. Với Next.js, lựa chọn quan trọng là static source policy đơn giản hay strict CSP dùng nonce theo request. Hãy cấp quyền tối thiểu, tránh 'unsafe-inline', hiểu chi phí dynamic rendering, triển khai Report-Only trước và xem CSP như một lớp giảm thiểu XSS—not như phép màu thay thế secure coding.
Hướng dẫn từng bước tích hợp đa ngôn ngữ (i18n) cho blog Next.js App Router và Notion API. Tối ưu SEO quốc tế, tự động phát hiện ngôn ngữ trình duyệt và giao diện chuyển đổi mượt mà.
Hướng dẫn chi tiết từng bước xây dựng blog cá nhân mượt mà, tối ưu SEO bằng Next.js App Router, Tailwind CSS và Notion API. Deploy miễn phí lên Vercel cho newbie.