Files
2026-09-08 12:35:40 +07:00

41 KiB
Raw Permalink Blame History

section, title, status, version, reviewer_notes
section title status version reviewer_notes
08 Thiết kế bảo mật approved 2

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 đã đủ)