Bảo mật và phân quyền khi tự host KDIGI CMS
Cập nhật lần cuối:

Bảo mật KDIGI CMS dựa trên ba lớp: cấu hình đúng khi triển khai (PAYLOAD_SECRET ≥ 32 ký tự, HTTPS, tạo super-admin ngay), phân quyền tối thiểu theo vai trò và site, và kiểm soát các endpoint công khai như bình luận. Dưới đây là checklist có căn cứ từ SECURITY.md và tài liệu phân quyền.
Tóm tắt nhanh
- 4 vai trò cộng dồn:
super-admin›admin›editor›author; người đầu tiên của bản cài mới làsuper-admin.- Ranh giới giữa các site là access predicate ở từng collection, không phải bộ chọn site trong admin.
PAYLOAD_SECRETdưới 32 ký tự ở production → server từ chối khởi động.- Khóa R2 chỉ ghi, không bao giờ đọc ngược qua API; chỉ
super-adminsửa được.- Nhập ảnh từ URL có SSRF guard; Markdown được làm sạch bằng allow-list.

1. Khi triển khai: những điều không được bỏ qua
| Hạng mục | Việc cần làm | Vì sao |
|---|---|---|
PAYLOAD_SECRET |
Chuỗi ngẫu nhiên ≥ 32 ký tự (openssl rand -hex 32) |
Ký phiên và API key; production không boot nếu ngắn hơn; đổi giá trị = đăng xuất mọi người |
PAYLOAD_PUBLIC_SERVER_URL |
Đúng origin https:// của CMS |
Sai → 403, mất cookie đăng nhập, nó cũng là tiền tố URL media |
| HTTPS | Reverse proxy (Caddy/nginx/Coolify) | CMS không tự làm TLS |
| Cổng 3000 | Tường lửa, chỉ proxy được truy cập | Image chạy next start ở 3000 |
| PostgreSQL | Chỉ mở trong mạng nội bộ; đổi POSTGRES_PASSWORD mặc định nếu cổng có thể tới |
Compose cục bộ dùng mật khẩu mặc định kdigi |
| Tài khoản đầu tiên | Tạo ngay sau khi triển khai | Trên DB trống, ai gửi form trước thành super-admin |
SEED_DEMO |
Không bật ở production | Tạo site và nội dung mẫu |
Ví dụ Caddy và các lệnh Compose xem ở tự host.
2. Mô hình vai trò
| Vai trò | Phạm vi | Làm được |
|---|---|---|
super-admin |
Mọi site | Bỏ qua bộ lọc site; duy nhất tạo/xóa người dùng và site, sửa cấu hình R2, gán site, chạy job |
admin |
Site được gán | Quản lý nội dung, đăng, duyệt bình luận, cài đặt site, xóa vĩnh viễn |
editor |
Site được gán | Quản lý nội dung, đăng, duyệt bình luận, chuyển vào thùng rác |
author |
Site được gán | Tạo nội dung; sửa/bỏ vào thùng rác chỉ bài của mình; không đăng |
Mỗi người dùng chỉ có một vai trò áp cho mọi site họ được gán (vai trò là toàn cục). Nếu cần một người là editor ở site A nhưng chỉ author ở site B, hãy tạo hai tài khoản.
Nguyên tắc tối thiểu quyền:
- Cộng tác viên viết bài →
author; biên tập viên duyệt và bấm Đăng. - Quản lý một khách hàng/chi nhánh →
adminchỉ gán site đó. - Giữ
super-admincho 1–2 người vận hành hạ tầng. - Lưu ý:
authorvẫn sửa đượctaxonomies,authors,menus,faqs,shortcodes,redirects,mediatrong site của mình. Hãy cân nhắc trước khi cấp.
Chi tiết từng vai trò: phân quyền người dùng.
3. Cách ly giữa các site
Có hai lớp, và chỉ một lớp là ranh giới thật:
| Lớp | Vai trò |
|---|---|
Bộ lọc danh sách và site selector trong admin (cookie payload-tenant) |
Tiện ích giao diện, không phải kiểm soát bảo mật |
Access predicate tenantScopedRead/Write/VersionsRead ở từng collection |
Ranh giới thật, ràng buộc where cho mọi thao tác |
ID site do client gửi (query tenantId, body, cookie) không bao giờ được tin trực tiếp: chỉ được chấp nhận với super-admin hoặc thành viên site đó. Khi không xác định được site, endpoint trả 400 thay vì chạy không giới hạn.
Cảnh báo cho frontend: request ẩn danh đọc nội dung đã xuất bản của mọi site nếu bạn không lọc. Luôn thêm where[tenant][equals]=<id>. Xem quản lý nhiều website.
4. API key và quyền truy cập máy–máy
usersbậtuseAPIKey; khóa xác thực như chính người dùng đó và chịu cùng quy tắc vai trò/site. Header:Authorization: users API-Key <key>.- Frontend công khai nên gọi API không cookie, không Authorization để chỉ thấy nội dung công khai.
- Tạo user riêng cho mỗi tích hợp (importer, CI), gán đúng site, đặt vai trò thấp nhất đủ dùng.
- Phiên đăng nhập admin kéo dài 180 ngày (
auth.tokenExpiration); thu hồi bằng cách đổiPAYLOAD_SECRET(cũng làm mất hiệu lực mọi API key).
5. Bí mật và trường nhạy cảm
| Trường | Quy tắc |
|---|---|
sites.r2.accessKeyId, secretAccessKey |
access.read: () => false, chỉ ghi qua POST /api/sites/:id/secret (super-admin); GET chỉ trả { field, hasValue } |
authors.email, comments.authorEmail |
Không bao giờ trả về |
createdBy, commentCount, kkStarRating |
Không sửa được từ API |
users.tenants[], role |
Chỉ super-admin sửa; người dùng không tự nâng quyền |
Lưu ý quan trọng: sites được đọc công khai, nên đừng bao giờ đặt bí mật vào trường thường của Sites. Nếu bạn viết endpoint tùy biến dùng Local API với overrideAccess: true, nó có thể đọc cả trường bí mật — hãy giới hạn dữ liệu trả về.
6. Endpoint công khai: bình luận và đánh giá
POST /api/comments/create: luôn tạopending; nội dung được làm sạch HTML;tenantSiteKeyphải khớp site của bài (sai → 403).RECAPTCHA_SECRET: nếu đặt, token được xác minh nhưng kết quả xấu chỉ ghi log, không chặn. Không đặt → production in cảnh báo[SECURITY]lúc khởi động.- Không có rate limit theo IP: đặt rate limit ở reverse proxy, và dùng
MODERATION_WEBHOOK_URLđể nhận thông báo bình luận mới. POST /api/ratings/submit: chỉ lưu băm SHA-256 của IP để chống bỏ phiếu lặp; đặt headerX-Client-IPtừ frontend server để lấy IP thật.
7. Nội dung và tải lên
- Markdown → HTML qua
markdown-itrồisanitize-htmltheo allow-list;script,style,iframe,style=vàon*không lọt ra HTML. POST /api/media/from-urlchặn SSRF: chỉhttp/https, phân giải DNS rồi từ chối nếu bất kỳ địa chỉ nào thuộc dải riêng/dự phòng (10/8, 172.16/12, 192.168/16, 127/8, 169.254/16…), tối đa 3 lần chuyển hướng (kiểm lại từng bước), timeout 15 giây, giới hạn 25 MB. Rủi ro còn lại (DNS rebinding) cần quyền admin.- File cục bộ trả
X-Content-Type-Options: nosniff, SVG kèm CSPdefault-src 'none'; sandbox.
8. Tài khoản demo công khai
Nếu bạn chạy sandbox công khai, liệt kê email trong DEMO_ACCOUNT_EMAILS: tài khoản đó không đổi được email, mật khẩu, API key hay tự xóa (403). Cho nó vai trò editor và một site demo. Đặt pnpm demo:reset chạy hằng đêm. Xem demo.
9. Báo lỗi bảo mật
Dự án chỉ hỗ trợ bản 1.x mới nhất. Báo lỗi qua GitHub Security Advisories (không mở issue công khai); cam kết phản hồi trong 3 ngày làm việc và cập nhật trạng thái trong 10 ngày làm việc. Đọc thêm ở trang mã nguồn mở.
Checklist nhanh
- [ ]
PAYLOAD_SECRET≥ 32 ký tự, đã sao lưu cùng database. - [ ] HTTPS, cổng 3000 và Postgres không lộ ra internet.
- [ ] Tài khoản
super-adminđã tạo; không bậtSEED_DEMO. - [ ] Mỗi người dùng chỉ được gán đúng site; cộng tác viên là
author. - [ ] Frontend lọc
tenanttrên mọi truy vấn ẩn danh. - [ ]
RECAPTCHA_SECRET+ rate limit ở proxy nếu mở bình luận. - [ ] Đã có kế hoạch sao lưu và nâng cấp.
Câu hỏi thường gặp
Người dùng của site A có xem được nội dung site B không?
Vai trò author có thể tự đăng bài không?
Ai cũng tạo được super-admin trên bản cài mới à?
Khóa R2 và API key có lộ qua API không?
Bình luận công khai có bị spam không và chặn thế nào?
Đọc tiếp
Phân quyền người dùng
Bốn vai trò super-admin, admin, editor, author; gán theo từng website; tác giả gửi bài chờ duyệt. Có tài khoản demo để thử ngay không cần cài đặt.
Tự host
Cài KDIGI CMS trên VPS của bạn bằng Docker Compose hoặc Coolify: yêu cầu hệ thống, biến môi trường, sao lưu, nâng cấp và các bước tăng cường bảo mật.
Sao lưu và nâng cấp KDIGI CMS: quy trình không mất dữ liệu
Quy trình sao lưu cơ sở dữ liệu và media, kiểm tra khôi phục, nâng cấp phiên bản bằng Docker, xem release notes và cách quay lui khi nâng cấp gặp sự cố.
Quản lý nhiều website
Một bảng quản trị cho nhiều website: tách dữ liệu theo site, cấu hình tên miền, thương hiệu, SEO riêng. Phù hợp agency và doanh nghiệp nhiều thương hiệu.