Đang tải…
Đang tải…
Gỡ 'unsafe-eval' khỏi Content-Security-Policy là việc đúng. Nó cũng làm mọi dynamic import chết ở localhost, và bản deploy thì không sao cả.

Mình gỡ 'unsafe-eval' khỏi header Content-Security-Policy vào một buổi chiều. Chạy lại dev server, mở trang chủ, và khối 3D ở hero là một ô trống. Console báo EvalError.
Điều làm mình mất khá lâu mới hiểu: bản production hoàn toàn không sao. Chính sách chặt hơn đã chặn đúng thứ nó cần chặn ở nơi có người dùng thật, và chặn nhầm ở nơi chỉ có mình.
Chỉ có một chỗ trong codebase cần tới nó: một đoạn new Function("debugger") dùng để phát hiện DevTools đang mở. Đó là một tính năng vui, không phải tính năng cần thiết. Khi nó chuyển thành tuỳ chọn và mặc định tắt, cả chỉ thị kia trở nên thừa.
Thừa với production. Với dev thì không.
Chunk mà dev server của webpack sinh ra được bọc trong eval(). Đó là cách nó gắn source map vào từng module để bạn debug thấy đúng file gốc. Cấm eval đi thì mọi lời gọi import() động đều chết ngay lúc chạy.
Cái bẫy nằm ở chữ "động". Trang vẫn dựng, layout vẫn đúng, hầu hết mọi thứ vẫn chạy. Chỉ những phần được code-split là hỏng, và code-split là thứ bạn áp dụng cho đúng những component nặng nhất: khối 3D, bộ dữ liệu địa chỉ tra cứu. Nghĩa là bạn mất khả năng kiểm chứng đúng những tính năng khó kiểm chứng nhất, trong khi trang trông vẫn bình thường.
`script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://accounts.google.com${
isDev ? " 'unsafe-eval' https://va.vercel-scripts.com" : ""
}`
Chuỗi thêm vào chỉ tồn tại khi isDev. Header gửi ra ở production giống hệt từng byte so với trước, còn localhost thì lấy lại được eval.
va.vercel-scripts.com đi kèm vì lý do cùng loại: ở production, gói analytics tải script từ chính origin của site, nên 'self' đã phủ. Ở dev nó đổi sang một bản debug đặt trên host ngoài, và chính sách kia sẽ chặn với đúng một dòng lỗi console.
'wasm-unsafe-eval' vào thế chỗ. Khối 3D cần biên dịch WASM của thư viện vật lý, và chỉ thị này cho phép đúng việc đó mà không mở lại eval cho JavaScript. Nó có từ Chrome 97, Safari 16.4 và Firefox 102, tức là phủ hết browserslist của dự án.
Còn 'unsafe-inline' thì phải ở lại, và đây mới là chỗ khó chịu. Next phát ra script bootstrap và payload RSC dạng inline trên mọi trang. Cách thay thế đúng chuẩn là dùng nonce, mà nonce phải khác nhau theo từng request. Trang prerender sẵn thì không có "từng request" nào để sinh nonce cả. Muốn bỏ 'unsafe-inline' thì phải bỏ luôn static rendering, và với site này thì đó là đánh đổi tệ hơn nhiều.
Đây là kiểu đánh đổi mà tài liệu về CSP hiếm khi nói thẳng. Chính sách hoàn hảo tồn tại trên giấy; chính sách bạn ship ra là chính sách chặt nhất còn tương thích với kiến trúc render của mình.
Sau lần đó mình thêm một quy tắc: bất cứ thay đổi nào chạm vào WebGL, WASM hay dynamic import đều phải kiểm chứng trên bản build production, không phải trên dev server.
Lý do thì đối xứng một cách khó chịu. Dev server nới lỏng chính sách, nên một thứ hỏng ở production có thể chạy ngon ở localhost. Nhưng chiều ngược lại cũng đúng, và đó chính là chuyện xảy ra ở đây: dev có thể hỏng vì một quy tắc mà production xử lý được. Cả hai chiều đều dẫn tới cùng một kết luận. Cái ô trống trên màn hình của bạn không nói được gì về cái ô đó trên màn hình người dùng.
Chưa có bình luận nào — hãy là người đầu tiên!