--- section: "08" title: Thiết kế bảo mật status: approved version: 2 reviewer_notes: "" --- # 8. Thiết kế bảo mật (Security Design) > **Vai trò của mục này:** rà soát chéo (cross-cutting review) trên các quyết định đã có ở mục 3 (kiến trúc), 4 (API), 5 (dữ liệu), 6 (luồng xử lý) — không thiết kế lại các mục đó. Mọi thiếu sót phát hiện được liệt kê ở §8.5 và trong `findings` của structured output để orchestrator cho chạy lại đúng mục. > > **Đầu vào:** `00-project-brief.md` (profile: `scale=large`, `hasPayment=true`, `hasPII=true`, `platforms=["web"]`, tuân thủ NĐ52/85, NĐ13/2023, PCI-DSS scope giảm, cloud=AWS, ngân sách/timeline chưa xác định), `02-phan-tich-yeu-cau.md` (NFR-04 Bảo mật, NFR-05 Tuân thủ), `03-kien-truc.md` (WAF/ALB/API Gateway, cô lập Payment Service, S3 mã hoá KYC, database-per-service), `04-api-design.md` **v3** (JWT Bearer, quy tắc ownership 4.1.1, mã lỗi `403 ERR_FORBIDDEN_OWNERSHIP`/`409 ERR_ACCOUNT_LINK_REQUIRED`, chống replay webhook, `Idempotency-Key` mở rộng), `05-thiet-ke-du-lieu.md` **v3** (cột **[PII]**/**[Payment]**, cột chống brute-force `user_account.failed_login_count/locked_until/last_failed_login_at`, bảng `audit_log` tại Audit & Compliance Service — 5.2.11), `06-luong-xu-ly.md` **v2** (luồng checkout/payment/KYC/payout/dispute/login đã cập nhật pre-signed URL KYC, kênh payout, sự kiện audit, nhánh khoá tài khoản). > > **Right-sizing:** vì `hasPayment=true` và `hasPII=true`, cả 4 mảng bảo mật (xác thực, bảo vệ dữ liệu, OWASP, tuân thủ) đều áp dụng đầy đủ, không có phần "không áp dụng" — riêng phạm vi PCI-DSS được **thu hẹp** (không lưu số thẻ, giao VNPay/Momo xử lý — xem §8.4). Không đề xuất công nghệ/ngân sách vượt ràng buộc mục 1 (cloud AWS, không SSO doanh nghiệp, ngân sách/timeline chưa xác định) — các đề xuất bên dưới đều dùng dịch vụ AWS chuẩn (KMS, Secrets Manager, WAF, GuardDuty, CloudTrail) hoặc thư viện mã nguồn mở; các hạng mục phát sinh chi phí đáng kể được ghi chú trade-off riêng. > > **(v2 — revision đồng bộ mục 4 v3/5 v3/6 v2, chỉ sửa tối thiểu):** (a) §8.1.1 bổ sung **chính sách khoá tài khoản (account lockout policy)** cụ thể — ngưỡng `failed_login_count`, thời lượng `locked_until` theo vai trò, cách reset — để mục 4 dùng khi bổ sung mã lỗi `423 ERR_ACCOUNT_LOCKED`; (b) §8.2 bổ sung mục 8.2.5 quyết định về redact PII trong `audit_log.before_json/after_json`, kiểm soát truy cập đọc `audit_log`, và retention; (c) §8.1.2/8.2/8.3 cập nhật tham chiếu sang mục 4 v3 (quy ước ownership 4.1.1, mã lỗi mới) và mục 5 v3 (cột mới, bảng `audit_log`); (d) §8.5 rà soát lại 10 finding v1 — đánh dấu finding đã giải quyết ở mục 4 v3/5 v3/6 v2, giữ lại finding chưa xử lý (F10 — MSK ACL, mục 3) và bổ sung 2-3 finding mới phát sinh từ mục 6 v2 (endpoint xem KYC document, mã lỗi khoá tài khoản — mục 4 đã hết vòng sửa, ghi nhận thủ công). ## 8.1 Xác thực & phân quyền (Authentication & Authorization) > Cơ chế JWT Bearer/OAuth2/scope theo actor đã được đặc tả ở mục 4.2 — **không lặp lại**, chỉ dẫn chiếu và bổ sung chi tiết triển khai (mục 4.2 v3 đã ghi rõ "cơ chế MFA chi tiết... chính sách khoá tài khoản thuộc mục 8"). ### 8.1.1 Xác thực (Authentication) | Hạng mục | Thiết kế | FR/Ghi chú | |---|---|---| | Mật khẩu | `bcrypt`/`argon2id` (đã có cột `password_hash` mục 5.2.1), độ dài tối thiểu 10 ký tự, kiểm tra chống mật khẩu rò rỉ (breach list, VD thư viện zxcvbn/HIBP k-anonymity API), không giới hạn ký tự đặc biệt | FR-01 | | Chống brute-force (account lockout) | **Đã triển khai đủ cột hỗ trợ ở mục 5 v3** (`user_account.failed_login_count`/`locked_until`/`last_failed_login_at`) và **luồng ở mục 6.1.6 v2** (tăng đếm khi sai, khoá khi vượt ngưỡng, reset khi đăng nhập thành công) — **chính sách cụ thể (ngưỡng/thời lượng theo vai trò) chốt tại §8.1.1a bên dưới**, kết hợp với rate-limit theo IP + captcha đã có ở mục 4.2 (defense-in-depth 2 lớp: theo tài khoản + theo IP) | FR-01, FR-27 | | JWT | Access token TTL 15-60 phút (đã chốt mục 4.2), refresh token TTL 7-30 ngày với **refresh token rotation** — mỗi lần refresh phát hành token mới, phát hiện tái sử dụng token cũ (reuse detection) → thu hồi toàn bộ chuỗi token của phiên đó (chống token bị đánh cắp dùng lại) | FR-01 | | Lưu trữ token phía client | Khuyến nghị: access token giữ trong bộ nhớ (memory) của SPA, refresh token trong cookie `HttpOnly; Secure; SameSite=Lax/Strict` (không dùng `localStorage` cho refresh token để giảm rủi ro XSS đánh cắp token dài hạn); nếu dùng cookie cho access token, bắt buộc thêm CSRF token (double-submit cookie) cho mọi request ghi | FR-01, FR-27 — **openQuestion:** mục 7 (UI) chưa xác nhận cơ chế lưu token cụ thể, cần đồng bộ khi thiết kế frontend | | MFA (FR-27) | TOTP (RFC 6238, ưu tiên hơn SMS OTP do rủi ro SIM-swap) bắt buộc cho `role=platform_admin`, khuyến khích cho `seller`; cấp 10 mã backup dùng một lần khi enroll; endpoint `/v1/auth/mfa/enroll`, `/v1/auth/mfa/challenge` đã có ở mục 4 — bổ sung: giới hạn 5 lần thử OTP sai/challenge token, challenge token TTL ngắn (≤5 phút) | FR-27, BR-12 | | OAuth2 Social login (FR-02) | **Đã triển khai ở mục 4 v3** (`POST /v1/auth/oauth/{provider}/callback`): xác thực tham số `state` (400 `ERR_OAUTH_STATE_INVALID` nếu thiếu/không khớp), xác minh `id_token` issuer/audience/expiry phía server trước khi tạo `OAuthIdentity`, và **không tự động liên kết (no auto-merge)** khi email trùng tài khoản email/password đã tồn tại — trả `409 ERR_ACCOUNT_LINK_REQUIRED`, yêu cầu xác minh sở hữu email trước khi merge tài khoản (chống account takeover) | FR-02 | | Session/logout | Refresh token bị thu hồi (đưa vào denylist Redis theo `jti` tới khi hết TTL) khi logout, đổi mật khẩu, hoặc Admin khoá tài khoản; đăng xuất tất cả thiết bị là hành động tuỳ chọn cho Customer (nice-to-have, không bắt buộc MVP) | FR-01 | ### 8.1.1a Chính sách khoá tài khoản (Account Lockout Policy) — v2 > Chốt theo yêu cầu người duyệt: quy định ngưỡng số lần đăng nhập sai và thời lượng khoá dựa trên cột `user_account.failed_login_count`/`locked_until`/`last_failed_login_at` (mục 5.2.1 v3), phục vụ luồng 6.1.6 v2 và để mục 4 bổ sung mã lỗi `423 ERR_ACCOUNT_LOCKED` khi có vòng sửa tiếp theo. | Vai trò (`user_account.role`) | Ngưỡng `failed_login_count` | Thời lượng khoá (`locked_until`) | Lý do khác biệt | |---|---|---|---| | `customer` | 5 lần sai liên tiếp | now + 15 phút | Số đông người dùng, ưu tiên trải nghiệp; kết hợp captcha sau 3 lần sai (đã có mục 4.2) giảm rủi ro trước khi chạm ngưỡng khoá | | `seller` | 5 lần sai liên tiếp | now + 15 phút | Cùng mức Customer; MFA khuyến khích (không bắt buộc) nên lockout theo mật khẩu là lớp phòng thủ chính | | `platform_admin` | **3 lần sai liên tiếp** | **now + 30 phút** | Quyền hạn cao nhất (scope `admin:*`) → ngưỡng thấp hơn, thời lượng khoá dài hơn Customer/Seller; rủi ro DoS (kẻ tấn công cố tình khoá tài khoản Admin đã biết email) được giảm thiểu vì Admin Backoffice chỉ truy cập qua VPN/IP allowlist (mục 3.3) — kẻ tấn công ngoài mạng nội bộ không gọi được `/v1/auth/login` với role Admin để kích hoạt khoá | | `ops_staff`, `csr` | 5 lần sai liên tiếp | now + 15 phút | Không có scope `admin:*` toàn cục; áp dụng như Customer/Seller là đủ, tránh phức tạp hoá chính sách không cần thiết | **Cơ chế cập nhật (áp dụng tại `POST /v1/auth/login`, khớp sequence 6.1.6 v2):** 1. Trước khi so khớp mật khẩu: nếu `locked_until` đã được đặt và `locked_until > now` → từ chối ngay, **không** so khớp mật khẩu (tránh side-channel timing), trả về mã lỗi tài khoản đang tạm khoá (đề xuất `423 ERR_ACCOUNT_LOCKED` — xem finding mục 4 ở §8.5) kèm thông tin thời điểm có thể thử lại (`retryAfter`), **không** tiết lộ email có tồn tại hay không trong thông báo lỗi. 2. Nếu `locked_until` đã qua (now ≥ `locked_until`) tại lần thử tiếp theo: coi như **tự động mở khoá** — reset `failed_login_count = 0` **trước khi** đánh giá mật khẩu của lần thử hiện tại (không cộng dồn từ chuỗi thất bại trước khi khoá), tránh khoá lặp vô hạn nhưng vẫn đánh giá công bằng lần thử mới. 3. Mật khẩu sai: `failed_login_count += 1`, `last_failed_login_at = now`; nếu `failed_login_count` vượt ngưỡng theo vai trò ở bảng trên → đặt `locked_until = now + thời lượng tương ứng`. 4. Mật khẩu đúng (dù trước đó có sai một vài lần chưa chạm ngưỡng): **reset `failed_login_count = 0`, `last_failed_login_at = NULL`** — không giữ lại lịch sử thất bại cũ sau khi xác thực thành công (đã khớp sequence 6.1.6 v2). 5. **Không có endpoint tự mở khoá sớm cho chính người dùng** ở MVP (đợi hết `locked_until`); trường hợp khẩn cấp (Customer/Seller liên hệ CSKH vì bị khoá do thao tác nhầm) xử lý thủ công qua nghiệp vụ vận hành nội bộ (CSR/Admin sửa trực tiếp `locked_until=NULL` qua công cụ nội bộ có kiểm soát, **không** qua API công khai) — không đề xuất thêm endpoint mới ở mục 4 cho luồng này vì tần suất thấp, tránh mở rộng bề mặt tấn công không cần thiết ở MVP. 6. **Khuyến nghị bổ sung (không bắt buộc)**: khi tài khoản chuyển sang `locked_until` lần đầu trong một khoảng thời gian, gửi thông báo email cho chủ tài khoản qua Notification Service (kênh sẵn có, chi phí không đáng kể) để cảnh báo khả năng bị dò mật khẩu — không chặn luồng chính nếu gửi thất bại. **Mã lỗi đề xuất cho mục 4** (chưa có ở mục 4 v3, xem finding §8.5): `423 ERR_ACCOUNT_LOCKED` — "Tài khoản tạm khoá do đăng nhập sai nhiều lần", response kèm `retryAfterSeconds` (tính từ `locked_until - now`), phân biệt với `401 ERR_AUTH_REQUIRED`/`ERR_AUTH_INVALID_TOKEN` (thiếu/sai token) và với thông báo sai email/mật khẩu thông thường (vẫn trả `401` chung chung không phân biệt "email không tồn tại" hay "sai mật khẩu" để tránh dò email hợp lệ — **chỉ** riêng lockout mới lộ trạng thái "đã bị khoá", chấp nhận đánh đổi UX vs. ẩn thông tin vì mức độ rủi ro thấp hơn lộ email tồn tại hay không). ### 8.1.2 Phân quyền (Authorization) — RBAC + kiểm soát ownership (ABAC nhẹ) - **RBAC theo scope**: giữ nguyên bảng scope/actor đã chốt ở mục 4.2 (`customer:*`, `seller:*`, `admin:*`, `ops:*`, `csr:*`) — Identity & Access Service là nguồn phát hành duy nhất, API Gateway/BFF enforce tại tầng biên trước khi route vào service nội bộ. - **Kiểm soát ownership (resource-level, bắt buộc ở tầng service, không chỉ ở Gateway)** — **đã chốt thành quy ước chính thức tại mục 4.1.1 v3** ("Quy tắc ownership (chống IDOR)"): mọi endpoint có tham số định danh tài nguyên gắn với một Customer/Seller cụ thể phải đối chiếu `sub`/`customerId`/`sellerId` trong JWT trước khi trả dữ liệu, vi phạm → `403 ERR_FORBIDDEN_OWNERSHIP` (phân biệt với `403 ERR_FORBIDDEN_SCOPE` khi thiếu quyền/scope). Mục 8 xác nhận và bổ sung chi tiết theo từng nhóm actor: - Customer: mọi truy vấn `order`, `cart`, `loyalty`, `wishlist`, `return-requests` phải so khớp `customerId` trong JWT `sub` claim với `customer_id` của resource — đã áp dụng đúng tại `GET/POST /v1/orders/{orderId}`, `GET /v1/payments/{paymentId}` (mục 4.1.5, 4.1.6 v3). - Seller: so khớp `sellerId` claim với `seller_id` của `product`, `order_seller`, `payout` — `GET /v1/seller/orders`, `GET /v1/seller/payouts` (mục 4.1.5, 4.1.8 v3) tự lọc theo `sellerId` trong JWT, không nhận `sellerId` qua query param. - CSR: chỉ thao tác `dispute` đã `assigned_csr_id` = chính mình hoặc chưa gán (`open`), không được sửa dispute đã gán cho CSR khác trừ khi Admin escalate — mục 4 hiện thiết kế truy cập `Dispute` toàn cục theo scope `csr:disputes:*` (không áp dụng ownership vì CSR xử lý tranh chấp toàn sàn theo phân công nội bộ); **khuyến nghị bổ sung ràng buộc `assigned_csr_id` ở tầng business logic** (không phải lỗi thiết kế API, mà là rule nghiệp vụ nội bộ — không tạo finding mới vì không phải IDOR giữa các Customer/Seller khác nhau). - Ops: giới hạn theo đơn hàng/khu vực được phân công (đã ghi nhận là "chi tiết RBAC ở mục 8" tại mục 4.2) — triển khai qua bảng phân công (assignment) tại Shipping & Fulfillment Service, kiểm tra trước khi cho phép `PATCH /v1/ops/orders/{orderId}/fulfillment`. - **Shipment tracking (`GET /v1/shipments/{shipmentId}/tracking`)**: **đã được vá ở mục 4.1.12 v3** — kiểm tra `customerId`/`sellerId` liên quan hoặc scope `ops:*`/`admin:*` toàn cục, trả `403 ERR_FORBIDDEN_OWNERSHIP` nếu không khớp (trước đây là Finding F1, nay đã giải quyết — xem §8.5). - **Admin/Ops Backoffice**: giới hạn mạng qua VPN/IP allowlist (đã quyết định ở mục 3.3) + bắt buộc MFA (role `platform_admin`) là 2 lớp phòng thủ độc lập (defense-in-depth); không cấp quyền truy cập DB Production trực tiếp trừ khẩn cấp có phê duyệt (đã ghi ở mục 3.3, giữ nguyên). - **Nguyên tắc chung**: mọi endpoint ghi dữ liệu (`POST`/`PUT`/`PATCH`/`DELETE`) đều phải qua middleware kiểm tra scope **và** ownership trước khi vào business logic — khuyến nghị triển khai như một lớp policy tập trung (VD OPA/Open Policy Agent hoặc middleware dùng chung trong BFF) để tránh mỗi service tự implement khác nhau và bỏ sót. ## 8.2 Bảo vệ dữ liệu (Data Protection) > Dựa trực tiếp trên danh sách cột **[PII]**/**[Payment]** đã đánh dấu ở mục 5.5 v3 — bảng dưới xác nhận biện pháp cụ thể cho từng nhóm, không lặp lại toàn bộ danh sách cột. ### 8.2.1 Mã hoá at-rest | Nhóm dữ liệu | Biện pháp | Ghi chú | |---|---|---| | Toàn bộ RDS PostgreSQL (database-per-service) | Mã hoá at-rest bằng AWS KMS (encryption at rest cấp instance/storage), khoá riêng theo service hoặc theo nhóm mức nhạy cảm (Payment/Commission/Seller/**Audit & Compliance** dùng CMK riêng, tách khỏi Review/Notification) | NFR-04, NFR-05 | | Cột nhạy cảm cao: `seller_bank_account.account_number`, `mfa_device.secret_encrypted`, `seller.tax_code`, `seller.business_license_number` | **Mã hoá tầng ứng dụng (application-level, AES-256-GCM)** bổ sung, khoá quản lý qua KMS envelope encryption — giảm rủi ro nếu bị SQL injection đọc thẳng DB hoặc nhân sự nội bộ (DBA) truy cập trực tiếp không qua ứng dụng | Khớp đề xuất mục 5.5; đây là control **bổ sung** so với mã hoá at-rest mặc định của RDS | | S3 (ảnh KYC, ảnh sản phẩm) | SSE-KMS, bucket KYC tách riêng, **không public**, versioning + cross-region replication (đã chốt mục 5.3.2); truy cập Admin xem tài liệu KYC qua **pre-signed URL TTL ≤5 phút** — **đã triển khai ở mục 6.1.5 v2** (Admin gọi Seller Management Service để sinh `viewUrl`, không truy cập trực tiếp object storage) | FR-17 — endpoint cụ thể (`GET .../kyc-documents/{documentId}/view-url`) chưa có ở mục 4 v3, xem finding §8.5 | | PII còn lại (`email`, `phone`, `full_name`, địa chỉ) | Mã hoá at-rest theo KMS mặc định của RDS là đủ (không cần application-level do tần suất truy vấn cao, đánh đổi hiệu năng) | Khớp mục 5.5 | | `user_account.failed_login_count`/`locked_until`/`last_failed_login_at` (mục 5.2.1 v3) | Không phải PII/Payment nhưng là dữ liệu bảo mật nhạy cảm — mã hoá at-rest mặc định của RDS là đủ; **kiểm soát ghi** chỉ qua luồng xác thực nội bộ (Identity & Access Service), không expose qua bất kỳ API đọc công khai nào (khớp ghi chú mục 5.5 v3) | FR-01, FR-27 | ### 8.2.2 Mã hoá in-transit & quản lý secret - **TLS 1.2+ bắt buộc** cho mọi kết nối: Client ↔ CDN/WAF/ALB, ALB ↔ API Gateway/BFF, BFF ↔ service nội bộ; bật HSTS ở tầng CDN/ALB. - **Secret/key management**: AWS Secrets Manager cho DB credentials, API key/secret VNPay/Momo/GHN/GHTK, OAuth client secret, SMTP/SMS provider key — không hard-code trong code/CI/CD; rotation tự động cho DB credentials, rotation thủ công có lịch (khuyến nghị 90 ngày) cho API key bên thứ ba (phụ thuộc khả năng rotate của từng đối tác). - **Payout batch file** (chứa `seller_bank_account.account_number`, `account_holder_name`): **đã có hướng dẫn kênh truyền ở mục 6.1.4 v2** (SFTP + mã hoá PGP hoặc API HTTPS của ngân hàng đối tác — ngân hàng cụ thể chưa chốt, ghi nhận là giả định/openQuestion tại mục 6, không phải finding bảo mật còn tồn đọng); không qua email trong mọi trường hợp. - **Message broker (Kafka/MSK)**: bật mã hoá in-transit (TLS) + ACL theo topic, đặc biệt các event chứa PII/tài chính (`OrderDelivered`, `PaymentConfirmed`, `PayoutScheduled`, và các domain event ghi `audit_log` như `KycDocumentVerified`/`DisputeResolved`/`PayoutRetried`/`SellerLocked` mục 5.2.11 v3) — chỉ consumer service liên quan được subscribe — **vẫn là finding chưa xử lý** vì mục 3 (kiến trúc) chưa cập nhật, xem **Finding F10** (§8.5, mục 3, low, không đổi so với v1). ### 8.2.3 Masking & giảm thiểu lộ dữ liệu - **Log/APM/tracing**: mọi log ứng dụng (CloudWatch Logs, APM traces) phải qua log-scrubber middleware để masking `email` (`c***@domain.com`), `phone` (ẩn 4 số giữa), `account_number` (chỉ hiện 4 số cuối), không log `password`, `secret_encrypted`, `gateway_transaction_ref` đầy đủ ở mức DEBUG trên môi trường Production. - **`payment.raw_gateway_response` (jsonb, mục 5.2.4)**: cần ràng buộc tại tầng ứng dụng chỉ lưu phần phản hồi phi thẻ (đã ghi chú ở mục 5) — bổ sung: whitelist field được lưu (không lưu nguyên payload thô nếu gateway trả kèm dữ liệu nhạy cảm ngoài dự kiến). - **Staging/Dev**: không chứa PII/KYC thật (đã chốt mục 3.3) — xác nhận lại quy trình anonymize dữ liệu khi sao chép Production → Staging (hash/mask `email`, `phone`, xoá `tax_code`/`account_number` thật, thay bằng dữ liệu giả lập nhất quán để giữ khả năng test). ### 8.2.4 Quyền của chủ thể dữ liệu (NĐ13/2023) - Quy trình xoá/ẩn danh (đã có ở mục 5.3.6) cần bổ sung: **xác thực danh tính người yêu cầu** trước khi xử lý (tránh giả mạo yêu cầu xoá tài khoản người khác), thời hạn phản hồi theo luật định, và log lại yêu cầu (ai yêu cầu, khi nào, xử lý bởi ai) vào `audit_log` (nay đã có bảng cụ thể ở mục 5.2.11 v3, xem §8.2.5). - **Quyền truy cập/xuất dữ liệu cá nhân ("right to access")**: brief/mục 2/5 chưa đề cập endpoint hoặc quy trình cho phép Customer/Seller yêu cầu xuất toàn bộ dữ liệu cá nhân của mình — đây là nghĩa vụ thường đi kèm NĐ13/2023, cần bổ sung (ít nhất là quy trình vận hành thủ công qua CSR ở giai đoạn đầu, không nhất thiết phải tự động hoá ngay). ### 8.2.5 Nhật ký kiểm toán (`audit_log`) — chính sách bảo mật (mới — v2) > Trả lời trực tiếp yêu cầu người duyệt (mục 3): quyết định cho bảng `audit_log` (mục 5.2.11 v3, Audit & Compliance Service). **(a) Redact/mask trường cực nhạy cảm trong `before_json`/`after_json` — QUYẾT ĐỊNH: có, bắt buộc.** - Nguyên tắc: giá trị nhạy cảm cao (`seller_bank_account.account_number`, `seller.tax_code`, `seller.business_license_number`/số CMND-CCCD trong `kyc_document`) **không bao giờ** được ghi ở dạng đầy đủ (raw) vào `audit_log`, kể cả khi đã mã hoá tầng ứng dụng ở nguồn (§8.2.1) — vì mục đích audit chỉ cần biết "đã thay đổi từ giá trị X sang Y", không cần giá trị đầy đủ. - Vị trí thực hiện masking: **tại service nguồn phát sự kiện** (Seller Management Service khi phát `KycDocumentVerified`/`SellerLocked`, Commission & Payout Service khi phát `CommissionRuleUpdated`/`PayoutRetried`), **trước khi** publish domain event lên Kafka/MSK — không để giá trị raw đi qua message broker dù chỉ tạm thời (khớp lưu ý ACL/mã hoá topic §8.2.2). Audit & Compliance Service chỉ ghi lại snapshot đã được masking từ nguồn, không tự giải mã/hiển thị lại giá trị gốc. - Quy tắc masking cụ thể: - `account_number`: chỉ giữ 4 ký tự cuối, còn lại thay bằng `*` (VD `**********1234`). - `tax_code`, `business_license_number`, số CMND/CCCD: giữ 3 ký tự đầu và 2 ký tự cuối, phần giữa thay bằng `*` (VD `079*******45`). - Các trường KYC dạng file (`file_url_s3`): **không** ghi đường dẫn S3 vào `audit_log` (tránh audit_log trở thành kênh truy cập gián tiếp tới object KYC) — chỉ ghi `document_type` và `verified_status` thay đổi. - Snapshot `before_json`/`after_json` bổ sung cờ `"_redacted": true` khi có trường bị masking, để người đọc audit biết dữ liệu đã qua xử lý, không phải thiếu sót ghi log. - Đây là quyết định của mục 8 nhưng **không** yêu cầu sửa lại schema `audit_log` ở mục 5 (kiểu cột `jsonb` đã đủ linh hoạt chứa giá trị đã masking) — chỉ là ràng buộc ở tầng ứng dụng khi ghi dữ liệu, không tạo finding hướng về mục 5. **(b) Kiểm soát truy cập đọc `audit_log` — QUYẾT ĐỊNH: chỉ scope `admin:audit:read` (Platform Admin), không cấp cho Ops/CSR.** - Lý do: `audit_log` chứa vết hành động nhạy cảm xuyên toàn sàn (duyệt KYC, khoá seller, cấu hình hoa hồng, quyết định dispute, retry payout) — phạm vi đọc rộng hơn phạm vi tác nghiệp thường nhật của Ops/CSR; giới hạn ở Platform Admin giảm bề mặt rủi ro lộ thông tin điều tra nội bộ. - Đề xuất scope mới `admin:audit:read` (không dùng chung `admin:*` để có thể tách nhỏ quyền sau này nếu marketplace cần vai trò "Security/Compliance Officer" riêng ở giai đoạn sau — hiện chưa có trong danh sách actor mục 1). - **Mục 4 chưa có endpoint đọc `audit_log`** (mục 5.5/5.2.11 v3 đã ghi chú giao cho `api-designer`, nhưng mục 4 v3 chưa bổ sung) — ghi nhận là finding mới hướng về mục 4 (xem §8.5), không tự thiết kế endpoint ở đây. **(c) Retention — QUYẾT ĐỊNH: giữ nguyên 5 năm cho phần lớn `audit_log`, khuyến nghị nâng lên 10 năm riêng cho nhóm hành động tài chính.** - Đa số hành động (KYC review, khoá/mở seller) phục vụ mục đích audit an ninh/vận hành — **5 năm** (như mục 5.3.6 đã chốt) là hợp lý và nhất quán với thông lệ audit an ninh. - **Riêng** các bản ghi `audit_log` có `resource_type` gắn trực tiếp tới nghiệp vụ tài chính (`commission_rule` khi thay đổi `hold_days`/`commission_percent`, `payout` khi retry, `dispute` khi quyết định là `refund`) nên áp dụng retention **10 năm**, khớp với retention của `payment`/`commission_transaction`/`payout` ở mục 5.3.6 (thông lệ chứng từ kế toán) — vì các bản ghi audit này là bằng chứng bổ trợ cho quyết định tài chính, tách rời hoặc xoá sớm hơn dữ liệu gốc có thể gây thiếu chứng cứ khi kiểm toán/thanh tra thuế. - Đây là **khuyến nghị điều chỉnh retention phân nhóm theo `resource_type`** khác với retention đơn nhất "5 năm" hiện có ở mục 5.3.6/5.2.11 — ghi nhận thành **finding hướng về mục 5** (§8.5, severity medium) vì đòi hỏi điều chỉnh chiến lược partition/archive (partition theo tháng đã có, chỉ cần logic archive job phân biệt theo `resource_type` khi tới mốc 5 năm), không tự sửa mục 5 ở đây. ## 8.3 Phòng chống rủi ro bảo mật (OWASP Top 10 — theo endpoint mục 4 & luồng mục 6) | OWASP 2021 | Endpoint/luồng cụ thể bị ảnh hưởng | Rủi ro | Biện pháp | |---|---|---|---| | **A01 – Broken Access Control** | `GET /v1/shipments/{shipmentId}/tracking` (4.1.12 v3) | IDOR — **đã vá ở mục 4 v3**: kiểm tra `customerId`/`sellerId` liên quan hoặc scope `ops:*`/`admin:*` toàn cục, `403 ERR_FORBIDDEN_OWNERSHIP` nếu không khớp (trước đây Finding F1, nay giải quyết) | Xác nhận giữ nguyên thiết kế hiện tại, không cần thay đổi thêm | | **A01 – Broken Access Control** | `PATCH /v1/admin/disputes/{disputeId}` (4.1.5), luồng 6.1.3 | CSR sửa dispute không do mình phụ trách | Kiểm tra `assigned_csr_id` = CSR hiện tại hoặc vai trò Admin — đây là rule nghiệp vụ nội bộ, không phải IDOR giữa khách hàng khác nhau, xem §8.1.2 | | **A02 – Cryptographic Failures** | `POST /v1/sellers/{sellerId}/kyc-documents`, `seller_bank_account`, `mfa_device.secret_encrypted` | Lộ dữ liệu tài chính/định danh nếu chỉ dựa mã hoá at-rest mặc định | Mã hoá tầng ứng dụng cho nhóm cột nhạy cảm cao (§8.2.1); áp dụng đồng thời cho snapshot ghi vào `audit_log` (redact — §8.2.5) | | **A03 – Injection** | `GET /v1/search/products?q=` (4.1.4) | OpenSearch query injection nếu ghép chuỗi trực tiếp từ `q` vào Query DSL | Dùng structured query builder (parameterize), không nối chuỗi thô; sanitize input, giới hạn độ dài `q` | | **A03 – Injection** | Toàn bộ endpoint ghi (checkout, KYC upload, commission rule) | SQL injection qua ORM lỏng lẻo, path traversal khi upload `multipart/form-data` KYC | Dùng ORM có parameterized query mặc định (không raw SQL nối chuỗi); validate MIME type/kích thước file KYC, quét virus (VD ClamAV/AWS trước khi lưu S3) | | **A04 – Insecure Design** | `POST /v1/checkout` (4.1.5) | Request không chứa giá — hệ thống tính giá server-side từ `Cart` (đã đúng thiết kế), tránh tamper giá phía client | Xác nhận giữ nguyên nguyên tắc "không tin dữ liệu giá từ client" cho mọi luồng tương lai (VD áp dụng cho `apply-coupon`, `loyalty/redeem`) | | **A04 – Insecure Design** | `PUT /v1/admin/commission-rules/{categoryId}` (4.1.8) | `holdDays` cho phép Admin override ngoài khoảng 3-7 (chỉ cảnh báo `422`, "vẫn cho phép... có xác nhận") | Bắt buộc log audit riêng (before/after + lý do) cho mọi lần override ngoài khoảng khuyến nghị — **đã có** qua event `CommissionRuleUpdated` ghi `audit_log` (mục 5.2.11/6.1.4 v2) | | **A05 – Security Misconfiguration** | API Gateway/BFF, mã lỗi chuẩn hoá (4.1.13) | Rò rỉ stack trace/chi tiết hệ thống qua `ERR_INTERNAL` | Response `500` không bao giờ trả chi tiết exception nội bộ ra client, chỉ `traceId` để tra log nội bộ (đã đúng thiết kế hiện tại, xác nhận giữ nguyên) | | **A05 – Security Misconfiguration** | Môi trường Dev/Staging (3.3) | Feature flag "mặc định bật" ở Dev có thể lộ tính năng chưa hoàn thiện nếu môi trường lộ ra ngoài | Xác nhận Dev/Staging không có DNS/IP public không cần thiết, chỉ qua VPN nội bộ | | **A06 – Vulnerable & Outdated Components** | Toàn bộ service (container hoá ECS Fargate/EKS) | Dependency có lỗ hổng đã biết | SCA scan (Trivy/Snyk/Dependabot) trong CI/CD — thuộc phạm vi mục 9, dẫn chiếu chéo, không thiết kế lại ở đây | | **A07 – Identification & Authentication Failures** | `/v1/auth/login`, `/v1/auth/mfa/challenge` (4.1.3) | Brute-force, credential stuffing | Rate limit (IP, mục 4.2) + account lockout theo vai trò (§8.1.1a, v2) + captcha — **đã có đủ cột hỗ trợ ở mục 5 v3, chỉ còn thiếu mã lỗi `423 ERR_ACCOUNT_LOCKED` ở mục 4 (finding §8.5)** | | **A08 – Software & Data Integrity Failures** | `/v1/payments/webhooks/{vnpay,momo}`, `/v1/webhooks/{ghn,ghtk}` (4.1.6, 4.1.12 v3) | Webhook giả mạo/replay nếu chỉ kiểm tra chữ ký mà không kiểm tra thời gian | **Đã triển khai ở mục 4 v3**: xác thực chữ ký + kiểm tra timestamp (từ chối nếu lệch quá 5 phút) + idempotency theo `gatewayTransactionRef` (trước đây Finding F3, nay giải quyết) | | **A09 – Security Logging & Monitoring Failures** | Toàn hệ thống, đặc biệt hành động Admin (KYC review, dispute resolution, commission override, payout retry, khoá/mở seller) | Thiếu audit trail tập trung để điều tra sự cố/gian lận | **Đã triển khai ở mục 5 v3/6 v2**: bảng `audit_log` tại Audit & Compliance Service, ghi qua domain event cho toàn bộ hành động nhạy cảm liệt kê (trước đây Finding F7, nay giải quyết); chính sách redact/access-control/retention chốt tại §8.2.5 (v2); giám sát/alerting realtime thuộc mục 9 (dẫn chiếu chéo) | | **A10 – SSRF** | Payment/Shipping Service gọi ra VNPay/Momo/GHN/GHTK (mục 3.4) | Rủi ro thấp vì URL đối tác cấu hình cứng (không nhận URL từ input người dùng); cần xác nhận không có endpoint nào nhận URL callback tuỳ ý từ client | Không phát hiện endpoint SSRF cụ thể trong mục 4/6 hiện tại; khuyến nghị giữ nguyên tắc "không bao giờ gọi ra ngoài theo URL do client cung cấp" khi mở rộng tính năng sau này | **CSRF**: vì API dùng JWT Bearer (không session cookie truyền thống) nên rủi ro CSRF thấp với access token lưu trong memory; nếu triển khai theo khuyến nghị §8.1.1 (refresh token trong cookie `HttpOnly`), bắt buộc bổ sung CSRF token (double-submit) cho các request ghi dùng cookie — cần đồng bộ với thiết kế frontend ở mục 7 (chưa có, xem `openQuestions`). **Rate limiting bổ sung**: `POST /v1/customers/me/loyalty/redeem` **đã yêu cầu `Idempotency-Key` bắt buộc ở mục 4.1.9 v3** (trước đây Finding F4, nay giải quyết); vẫn khuyến nghị rate limit theo user cho endpoint này và `POST /v1/cart/apply-coupon` để chống dò mã coupon/lạm dụng đổi điểm hàng loạt bằng script (khuyến nghị bổ sung, không phải lỗi thiết kế đã có). ## 8.4 Tuân thủ (Compliance) | Quy định | Trạng thái áp dụng | Ghi chú kỹ thuật | |---|---|---| | **PCI-DSS** | **Áp dụng, scope thu hẹp** (không lưu số thẻ — đã xác nhận kiến trúc mục 3.1, dữ liệu bảng mục 5.2.4) | Nếu VNPay/Momo tích hợp theo hình thức **redirect** (không nhúng iframe/form nhập thẻ trên domain của sàn), scope tương ứng **SAQ A** (đơn giản nhất) — cần xác nhận hình thức tích hợp cụ thể với 2 gateway (openQuestion); dù scope giảm vẫn khuyến nghị: WAF với OWASP Core Rule Set (đã có ở mục 3.2), quét lỗ hổng bên ngoài định kỳ (ASV scan hàng quý) nếu domain thanh toán thuộc phạm vi SAQ yêu cầu, và pentest ứng dụng hàng năm — các hạng mục này có chi phí, cần xác nhận ngân sách (ngân sách/timeline hiện "chưa xác định" theo brief) | | **NĐ13/2023 (Bảo vệ dữ liệu cá nhân)** | Áp dụng đầy đủ (hasPII=true) | Đã có: mã hoá, retention (mục 5.3.6), right-to-delete (mục 5.3.6 + bổ sung §8.2.4), audit trail cho yêu cầu xoá (§8.2.5, `audit_log`). Còn thiếu: DPIA (Data Protection Impact Assessment) chưa thực hiện — khuyến nghị thực hiện trước go-live; cơ chế consent quản lý (marketing email/SMS opt-in/opt-out) — đã có `notification-preferences` (FR-12) nhưng chưa rõ có tách riêng consent marketing vs giao dịch bắt buộc hay không — **openQuestion** | | **NĐ52/85 (thông báo website TMĐT marketplace)** | Áp dụng — chủ yếu là nghĩa vụ pháp lý/hành chính (đăng ký với Bộ Công Thương), không phải control kỹ thuật của mục 8 | Yêu cầu kỹ thuật liên quan duy nhất: hiển thị thông tin đăng ký/logo xác nhận ở footer — thuộc mục 7 (UI), không lặp lại ở đây | | **Tuân thủ nội bộ khác** | Không áp dụng SSO doanh nghiệp/IdP liên kết (đã chốt "không có khách hàng B2B enterprise" ở brief) | Giữ nguyên theo ràng buộc mục 1, không đề xuất bổ sung SAML/OIDC federation ở MVP | **Trade-off/chi phí cần lưu ý** (không vượt ràng buộc ngân sách mục 1, chỉ nêu để chủ dự án cân nhắc khi ngân sách được xác định): - Mã hoá tầng ứng dụng cho cột nhạy cảm cao (§8.2.1) + redact khi ghi `audit_log` (§8.2.5) làm tăng độ phức tạp phát triển/vận hành (quản lý key rotation, chi phí CPU giải mã, logic masking tại nhiều service nguồn) — chấp nhận được ở quy mô "large" có PII/Payment, nhưng cần thời gian dev bổ sung so với chỉ dùng mã hoá at-rest mặc định. - ASV scan quý + pentest năm + AWS GuardDuty/Security Hub/Macie (phát hiện PII ngoài ý muốn) là chi phí vận hành liên tục, không bắt buộc về mặt kỹ thuật để hệ thống chạy nhưng khuyến nghị mạnh cho quy mô/loại dữ liệu hiện tại — cần xác nhận ngân sách bảo mật vận hành hàng năm (hiện brief chưa có con số). - OPA/policy-as-code cho kiểm soát ownership tập trung (§8.1.2) là lựa chọn kiến trúc bổ sung có thể triển khai đơn giản hơn bằng middleware tự viết nếu muốn giảm chi phí học/vận hành thêm một thành phần mới — nêu như một lựa chọn, không bắt buộc. - Retention 10 năm riêng cho nhóm `audit_log` tài chính (§8.2.5c) làm tăng chi phí lưu trữ dài hạn (dù đã partition theo tháng) — chi phí storage lạnh (S3 Glacier archive sau khi hết hạn truy vấn nhanh) là hợp lý, cần chủ dự án xác nhận khi có ngân sách vận hành cụ thể. ## 8.5 Rủi ro phát hiện & khuyến nghị > **(v2)** Rà soát lại toàn bộ 10 finding của v1: 9/10 đã được giải quyết ở mục 4 v3 / 5 v3 / 6 v2 (liệt kê tại bảng "Finding đã giải quyết" bên dưới, giữ lại để truy vết lịch sử — không tính vào `findings` của structured output). 1 finding cũ (F10 — MSK ACL) và 3 finding mới phát sinh từ mục 6 v2 vẫn còn tồn đọng, được liệt kê ở bảng "Finding còn tồn đọng" — đây là các finding trả về trong structured output. ### Finding đã giải quyết (lịch sử, không còn hành động cần thiết) | # | Mục đã sửa | Vấn đề gốc (v1) | Trạng thái v2 | |---|---|---|---| | F1 | 04 v3 | `GET /v1/shipments/{shipmentId}/tracking` thiếu ràng buộc sở hữu → IDOR | **Đã giải quyết** — mục 4.1.12 v3 bổ sung kiểm tra ownership, `403 ERR_FORBIDDEN_OWNERSHIP` | | F2 | 04 v3 | `X-Guest-Session-Id` chưa quy định CSPRNG/cookie flags | **Đã giải quyết** — mục 4.1.1 v3: CSPRNG ≥128-bit, cookie `HttpOnly/Secure/SameSite=Lax`, rate-limit riêng theo IP cho endpoint ghi Cart Guest | | F3 | 04 v3 | Webhook thiếu chống replay (timestamp/nonce) | **Đã giải quyết** — mục 4.1.6 v3: kiểm tra timestamp lệch ≤5 phút + idempotency theo `gatewayTransactionRef` | | F4 | 04 v3 | `loyalty/redeem` thiếu `Idempotency-Key` | **Đã giải quyết** — mục 4.1.9 v3 bổ sung `Idempotency-Key` bắt buộc | | F5 | 04 v3 | OAuth callback thiếu kiểm tra `state`/xử lý trùng email | **Đã giải quyết** — mục 4.1.3 v3: `state` bắt buộc (`400 ERR_OAUTH_STATE_INVALID`), `409 ERR_ACCOUNT_LINK_REQUIRED` khi trùng email, không auto-merge | | F6 | 05 v3 | `user_account` thiếu cột chống brute-force | **Đã giải quyết** — mục 5.2.1 v3 bổ sung `failed_login_count`/`locked_until`/`last_failed_login_at`; chính sách ngưỡng/thời lượng chốt tại §8.1.1a (v2) | | F7 | 05 v3 | Thiếu bảng audit log tập trung | **Đã giải quyết** — mục 5.2.11 v3 bổ sung `audit_log` tại Audit & Compliance Service; chính sách redact/access/retention chốt tại §8.2.5 (v2) | | F8 | 06 v2 | KYC document chưa có cơ chế xem an toàn (pre-signed URL) | **Đã giải quyết** — sequence 6.1.5 v2 bổ sung bước sinh pre-signed URL TTL ≤5 phút; **lưu ý phụ**: endpoint tương ứng chưa có ở mục 4 v3 → xem finding mới #F11 bên dưới | | F9 | 06 v2 | Payout batch file thiếu kênh truyền/mã hoá cụ thể | **Đã giải quyết (ở mức thiết kế)** — sequence 6.1.4 v2 nêu kênh SFTP+PGP hoặc API HTTPS ngân hàng đối tác; ngân hàng cụ thể vẫn là giả định/openQuestion tại mục 6 (không phải finding bảo mật còn tồn đọng) | ### Finding còn tồn đọng (trả về trong `findings` của structured output) | # | Mục cần sửa | Vấn đề | Mức độ | Khuyến nghị | |---|---|---|---|---| | F10 | 03 | Sơ đồ kiến trúc (3.2) chưa đề cập ACL/mã hoá theo topic cho Message Broker (Kafka/MSK), trong khi nhiều event mang dữ liệu tài chính/PII gián tiếp (`PaymentConfirmed`, `PayoutScheduled`, `OrderDelivered`, và nay thêm các domain event ghi `audit_log`) | Low (không đổi so với v1) | Bổ sung: bật TLS in-transit cho MSK, ACL theo topic giới hạn consumer là service liên quan, không cho mọi service subscribe toàn bộ topic | | F11 | 04 | Mục 4 chưa có endpoint cho Admin lấy pre-signed URL xem một `KYCDocument` cụ thể (sequence 6.1.5 v2 đã mô tả cơ chế nhưng thiếu endpoint tương ứng, VD `GET /v1/admin/sellers/{sellerId}/kyc-documents/{documentId}/view-url`) | Low — **mục 4 đã hết vòng sửa (v3, approved), ghi nhận để xử lý thủ công/vòng sau** | Bổ sung endpoint trả `{ viewUrl, expiresInSeconds<=300 }`, không trả `file_url_s3` trực tiếp | | F12 | 04 | Mục 4 chưa có mã lỗi cho trường hợp tài khoản bị khoá tạm do vượt ngưỡng đăng nhập sai (`user_account.locked_until`, mục 5.2.1 v3) — chính sách ngưỡng/thời lượng đã chốt tại §8.1.1a (v2) | Low — **mục 4 đã hết vòng sửa (v3, approved), ghi nhận để xử lý thủ công/vòng sau** | Bổ sung mã lỗi `423 ERR_ACCOUNT_LOCKED` kèm `retryAfterSeconds`, áp dụng tại `POST /v1/auth/login` | | F13 | 04 | Mục 4 chưa có endpoint đọc `audit_log` (mục 5.2.11/5.5 v3 đã ghi chú giao cho `api-designer` nhưng chưa được bổ sung ở mục 4 v3) | Low — cùng lý do F11/F12, ghi nhận thủ công/vòng sau | Bổ sung endpoint dạng `GET /v1/admin/audit-logs` (scope `admin:audit:read` — xem §8.2.5b), hỗ trợ filter theo `resource_type`/`resource_id`/`actor_id`/khoảng thời gian | | F14 | 05 | Retention `audit_log` hiện đồng nhất 5 năm (mục 5.2.11/5.3.6) — khuyến nghị phân nhóm theo `resource_type`: giữ 5 năm cho hành động vận hành (KYC, khoá seller), nâng lên 10 năm cho hành động gắn trực tiếp tài chính (`commission_rule`, `payout`, `dispute` quyết định refund) để nhất quán với retention `payment`/`payout` (mục 5.3.6) | Medium | Điều chỉnh logic archive/xoá của `audit_log` theo `resource_type` thay vì một mốc retention duy nhất; không cần đổi schema (cột `jsonb`/`resource_type` đã đủ) |