diff --git a/Docs/Proposals/2026-10-28-extension-data-source.html b/Docs/Proposals/2026-10-28-extension-data-source.html new file mode 100644 index 00000000..11f6125a --- /dev/null +++ b/Docs/Proposals/2026-10-28-extension-data-source.html @@ -0,0 +1,306 @@ + + + + + + + Extension làm nguồn dữ liệu + + + +
+ Đề xuất, chưa code + Chờ JOY trả lời 7 câu +

Mở rộng extension thành nguồn dữ liệu chính

+

Ngày 28/10/2026 - Issue DOS/Rate#1175 - Bản tóm tắt tiếng Anh: 2026-10-28-extension-data-source.md - Người soạn: session con của Rate Core PO

+ +
+

Tóm tắt một đoạn

+

Crawler đang đụng tường bot (TikTok tạm dừng ở RS#658, Lazada search bị trả về trang chặn (punish page) ở RS#659, Shopee chỉ chạy được qua một runner đã đăng nhập). Trình duyệt thật của người dùng không gặp những tường này vì chính người dùng mở trang. Đề xuất: phương án B, cho extension gửi một quan sát tối thiểu (giá, tồn kho, mã SKU, URL, thời điểm, sàn, không có dữ liệu cá nhân) khi người dùng đã đồng ý, lưu vào vùng cách ly, và chỉ đưa lên giá công khai sau khi Rate Services xác minh chéo với crawler hoặc với người dùng độc lập khác. Crawler vẫn giữ làm nguồn khởi động và đối chứng. Extension vẫn công khai (người dùng tải về thì giải nén ra đọc được), còn luật chống đầu độc và chấm điểm tin cậy nằm ở DOS/Rate-Services private. Extension không bao giờ tự tải trang người dùng không mở, không né bot, không giải captcha.

+
+ +

1. Hiện trạng apps/extension (đọc từ mã, nhánh dev ngày 28/10)

+ +

1.1. Extension đọc gì

+ + +

1.2. Hai luồng gửi lên server

+
Mỗi lần mở trang sản phẩm (luôn chạy, không cần opt-in): + GET /api/v1/rate/extension/lookup?platform&url&title&price&... + -> chỉ đọc, trả giá lịch sử, nơi bán khác, phán định giá ảo. Không ghi DB. + +Chỉ khi người dùng bật "Đóng góp phát hiện sản phẩm mới (Opt-In)" (mặc định TẮT): + POST /api/v1/rate/extension/ingest { platform, externalItemId, externalShopId, + url, title, selectedVariant, price, originalPrice, ratingStar, + ratingCount, userConsentOptIn: true } + -> Zod + giới hạn 15 lần/phút/IP (bộ nhớ trong của từng instance serverless, + không phải giới hạn toàn cục) + chặn phụ kiện + kiểm host khớp sàn + -> RPC rate.enqueue_crowd_verify_signal (không dùng service role) + -> chèn 1 job 'extension_crowd_verify' vào rate.crawl_jobs + (trần 120 job/phút toàn hệ thống, đây là giới hạn toàn cục duy nhất) + -> worker Rate Services nhận job như một yêu cầu CÀO LẠI URL đó
+ +

1.3. "Cách ly" hiện tại nghĩa là gì

+ + +
+

1.4. Những điểm lệch phát hiện khi đọc mã (cần sửa dù JOY chọn phương án nào)

+
    +
  1. Nhãn opt-in trong side panel nói "Chỉ gửi URL sản phẩm công khai đã cắt sạch tham số theo dõi", nhưng payload thật gửi mười trường dữ liệu (cộng cờ đồng ý): nền tảng, mã sản phẩm, mã shop, URL, tiêu đề, phiên bản, giá, giá gốc, số sao, số lượt đánh giá. Lời công bố phải khớp dữ liệu gửi đi; Chrome Web Store coi công bố nổi bật trong giao diện là điều kiện để sự đồng ý có giá trị [6].
  2. +
  3. CHROMEWEBSTORE.md giải trình quyền tabs và host localhost:3000 nhưng manifest.json không có hai quyền này; ngược lại manifest có năm host mà tài liệu không giải trình: TikTok, Hoàng Hà, Di Động Việt, Phong Vũ, www.rate.vn. Mục công bố quyền riêng tư của tài liệu này cũng chỉ kể 6 tên miền và nói extension "chỉ chạy trên trang chi tiết sản phẩm", trong khi content script khớp mọi đường dẫn của 10 sàn (việc lọc trang sản phẩm diễn ra trong mã, sau khi script đã chạy).
  4. +
  5. Host https://*.tiktok.com/* cho content script chạy trên toàn bộ TikTok (cả video, tin nhắn), rộng hơn mức cần cho trang sản phẩm. Match pattern hỗ trợ giới hạn theo đường dẫn, nên có thể thu hẹp.
  6. +
  7. Trang /privacy-policy của rate.vn chưa nhắc tới extension.
  8. +
  9. Luồng lookup mặc định, không cần opt-in, đã gửi URL, tiêu đề, giá, mã sản phẩm, mã shop (và IP theo bản chất HTTP) lên rate.vn ở mọi lần mở trang sản phẩm. Chính sách Chrome cho phép gửi hoạt động duyệt web khi phục vụ tính năng người dùng thấy và được công bố nổi bật [6]; giả định của người soạn là luồng này hợp lệ nếu được công bố rõ, nhưng nó cũng là dữ liệu duyệt web theo Luật 91/2025/QH15, nên cần đưa vào câu hỏi cho ý kiến pháp lý (mục 4.7).
  10. +
+

Chưa xác minh extension đã được phát hành trên Chrome Web Store hay chưa.

+
+ +

2. Honey, Keepa, BeeCost lấy dữ liệu thế nào (chỉ từ nguồn công khai)

+
+ + + + + + + + + + +
Sản phẩmĐiều đã công bố hoặc đã được kiểm chứngBài học cho Rate
Honey (PayPal)Trang "Data and privacy" của Honey nói họ thu thập thông tin sản phẩm chung như giá và tình trạng còn hàng để chạy lịch sử giá và Droplist, chỉ trên các trang mua sắm họ hỗ trợ, và dữ liệu sự kiện không chứa thông tin định danh [1]. Tổ chức datarequests.org cho rằng Honey thu thập lịch sử duyệt web nhiều hơn mức chính sách nói và đã khiếu nại lên cơ quan bảo vệ dữ liệu [2].Mô hình gần nhất với đề xuất: extension đọc giá trên trang người dùng xem. Rủi ro uy tín lớn nhất là làm nhiều hơn điều mình công bố.
KeepaTrang Firefox Add-ons của Keepa nói extension gửi mã ASIN của sản phẩm đang xem để vẽ biểu đồ và không lưu gì về người dùng [3]. Nhà nghiên cứu Wladimir Palant (2021) ghi nhận extension giữ kết nối WebSocket với một mã người dùng cố định, gửi số sao và hạng bán, và nhận lệnh từ server để tải nền các trang Amazon người dùng không mở rồi gửi dữ liệu về [4]. Một thảo luận trên nhóm Chromium Extensions cũng nêu việc này [5]. Keepa còn cung cấp dữ liệu lịch sử giá qua API cho bên ngoài [5b].Ví dụ cần tránh: biến máy người dùng thành crawler phân tán. Đó đúng là logic cào dữ liệu, trái tinh thần chính sách store, và không được đặt trong DOS/Rate. Rate chỉ đọc trang người dùng tự mở.
BeeCost (Việt Nam)Có extension cho Chrome và Cốc Cốc, hiện lịch sử giá trên Shopee, Tiki, Lazada, Sendo [7][8]; nhà phát triển nói có hơn 100 triệu sản phẩm để so sánh [9]. Một bài đánh giá nói khả năng lấy dữ liệu tốt nhất trên Shopee, chưa ổn định trên Lazada và FPT Shop [10]. Không tìm thấy tài liệu công khai nào mô tả cách BeeCost thu thập dữ liệu hay chính sách quyền riêng tư của họ.Chứng minh người dùng Việt Nam chấp nhận extension lịch sử giá, và Cốc Cốc là kênh phân phối đáng kể. Không suy đoán thêm về cách họ làm.
+

Các nguồn [2], [4], [5] là bên thứ ba và có thể đã cũ; chúng được dẫn để thấy rủi ro, không phải để kết luận hành vi hiện tại của các sản phẩm đó.

+ +

3. Vì sao trình duyệt thật né được tường bot

+ + +

4. Những gì thay đổi nếu extension là nguồn chính

+ +

4.1. Payload quan sát tối thiểu (đề xuất ExtensionObservation v1)

+
+ + + + + + + + + + + +
TrườngÝ nghĩaGhi chú
schema_versionPhiên bản payloadBắt buộc, để server từ chối bản cũ.
marketplaceSàn (10 giá trị hiện có)Server kiểm lại từ host của URL.
listing_urlURL chuẩn hóa, không queryĐã cắt tham số theo dõi như hiện nay.
external_item_id, external_shop_idMã listing và mã shop của sànMã của gian hàng, không phải của người dùng.
sku_id, variant_labelMã phiên bản (ví dụ model id của Shopee) và nhãn phiên bản đang chọnKhớp với mô hình Item, Variant, Listing (#1129).
price_vnd, list_price_vndGiá bán hiển thị và giá gạch ngangMục tiêu: chỉ lấy giá công khai chính của trang, bỏ giá sau voucher, xu, giá thành viên, vì chúng phụ thuộc tài khoản. Extractor hiện chưa phân biệt được hai loại giá này; đây là việc của M2 và cần kiểm từng sàn.
stock_state, stock_qtyCòn hàng, hết hàng, sắp hết, không rõ; số lượng nếu trang hiệnTrường mới, extractor hiện chưa đọc.
observed_atThời điểm client thấy giáServer ghi thêm received_at và tin received_at hơn observed_at.
extractorNguồn trích xuất (JSON-LD hay DOM) và phiên bản extensionĐể loại bản extractor hỏng.
submitter_idMã theo lượt cài, do server cấp khi người dùng đồng ý; server tự ghi thời điểm cấpCần cho chống đầu độc. Coi là dữ liệu cá nhân đã bút danh hóa (pseudonymous), lưu riêng, không công khai, người dùng yêu cầu xóa được. Kẻ tấn công vẫn xin được mã mới tùy ý, nên mã này chỉ giới hạn được client trung thực; phòng thủ thật nằm ở 4.4.
+

Không bao giờ gửi: cookie, tên tài khoản sàn, giỏ hàng, đơn hàng, địa chỉ, số điện thoại, từ khóa tìm kiếm, referrer, query string, nội dung tab khác, lịch sử duyệt. Bỏ ratingStar, ratingCount khỏi luồng này trừ khi JOY muốn giữ.

+

IP: không lưu IP nguyên. Để kiểm "người gửi độc lập" ở 4.4, phía nhận chỉ giữ một mã băm có muối của dải mạng (ví dụ /24 hoặc ASN) trong thời gian ngắn (vài tuần), không gắn vào giá công khai. Mã băm này vẫn là dữ liệu cá nhân và phải được công bố ở 4.2.

+ +

4.2. Lời đồng ý và chính sách quyền riêng tư

+ + +

4.3. Rủi ro duyệt Chrome Web Store và Edge Add-ons

+ + +

4.4. Chống đầu độc dữ liệu

+

Nguyên tắc: mọi bản gửi đều là không tin cậy. Mã extension công khai và chạy trên máy người dùng, ai cũng sửa được hoặc gọi thẳng API. Không có cách nào chứng thực client.

+
+

Lớp công khai (DOS/Rate, ở ingest)

    +
  • Kiểm định dạng, miền giá trị, host khớp sàn, chặn phụ kiện.
  • +
  • Giới hạn tần suất thô theo IP và theo submitter_id (chỉ chặn được client trung thực hoặc lỗi; kẻ tấn công đổi mã được).
  • +
  • Chỉ lưu vào vùng cách ly, không chấm điểm, không quyết định.
  • +
+

Lớp private (DOS/Rate-Services)

    +
  • Điểm tin cậy người gửi (tuổi của mã tính theo thời điểm server cấp, lịch sử khớp, tài khoản rate.vn nếu có). Mã mới bắt đầu ở mức tin cậy thấp, nên đổi mã liên tục không có lợi.
  • +
  • Đối chiếu với crawler và với người gửi độc lập khác (khác mã người gửi, khác mã băm dải mạng, khác thời điểm).
  • +
  • Phát hiện ngoại lai so với giá gần đây, giới hạn biên độ thay đổi.
  • +
  • Ngưỡng cụ thể, danh sách chặn, trọng số: không công bố.
  • +
+
+

Cách ly tới khi được xác nhận: một quan sát chỉ lên giá công khai khi khớp với crawler trong biên sai số, hoặc khớp với đủ số người gửi độc lập có điểm tin cậy. Một kẻ tạo hàng nghìn lượt cài giả vẫn phải vượt qua tính độc lập và lịch sử tin cậy.

+

Lưu ý về trần hiện tại: trần 120 job/phút toàn hệ thống trong RPC hiện nay hợp với "yêu cầu cào lại" nhưng không hợp với nguồn chính: vừa là nút thắt, vừa cho phép một kẻ tấn công làm đầy trần để chặn người dùng thật. Phương án B cần một bảng cách ly riêng có giới hạn theo người gửi thay vì trần toàn cục.

+ +

4.5. Khởi động lạnh (cold start)

+ + +

4.6. Độ phủ theo sàn

+
+ + + + + + +
SànCrawler hiện nay (theo PO Rate Services)Extension thêm được gìRủi ro riêng
ShopeeChỉ chạy qua một runner đã đăng nhậpGiá trị cao nhất: dữ liệu thật từ nhiều ngườiClass CSS bị làm rối, đổi thường xuyên (selector hiện có như .pqTWkA); giá phụ thuộc tài khoản nhiều
LazadaSearch bị trả về trang chặn (RS#659)Giá trang chi tiết khi người dùng xemExtension không thay được search discovery
TikiChưa có báo cáo bị chặn (chưa xác minh)Chủ yếu để đối chiếuThấp
TikTok ShopTạm dừng (RS#658)Có thể là nguồn duy nhấtPhải thu hẹp quyền host; trang sản phẩm hay mở trong app chứ không trên web, độ phủ có thể thấp
CellphoneS, TGDĐ, FPT Shop, Hoàng Hà, Di Động Việt, Phong VũChưa có báo cáo bị chặn (chưa xác minh)Đối chiếu, làm tươi giá giữa các lượt càoGiá và tồn kho có thể khác theo tỉnh; đề xuất chỉ lấy giá toàn quốc, bỏ tồn kho theo tỉnh để không phải gửi vị trí
+ +

4.7. Pháp lý và điều khoản sàn

+ + +

5. Câu hỏi của JOY: vì sao extension công khai còn crawler thì private?

+
+

Trả lời ngắn: extension không phải crawler. Nó chỉ đọc trang người dùng đang xem, khi họ đồng ý, và gửi lên rate.vn. Phần nhạy cảm không nằm ở việc đọc trang, mà nằm ở cách server quyết định tin bản gửi nào. Phần đó private.

+
+
    +
  1. Client buộc phải lộ. Extension được phát cho mọi người; tải file .crx về giải nén là đọc được toàn bộ mã. Giấu repo không giấu được client, chỉ làm mất lợi ích của open-core: người dùng và người duyệt store kiểm được extension làm đúng điều nó nói. Với một extension đọc trang mua sắm, sự minh bạch này là điểm bán hàng (ngược với các tranh cãi của Honey và Keepa ở mục 2).
  2. +
  3. Extension không chứa gì thuộc loại crawler. Không tải trang người dùng không mở, không proxy, không giải captcha, không né phát hiện bot, không gọi API nội bộ của sàn. Selector đọc giá trên trang người dùng xem không phải bí mật: ai mở DevTools cũng thấy.
  4. +
  5. Phía nhận mới là chỗ bị tấn công. Kẻ đầu độc không cần sửa extension, họ gọi thẳng API. Nếu họ đọc được ngưỡng, trọng số, cách đối chiếu với crawler, họ sẽ gửi dữ liệu nằm vừa dưới ngưỡng. Vì vậy chấm điểm tin cậy, đối chiếu crawler, ngưỡng và giới hạn chi tiết nằm ở DOS/Rate-Services.
  6. +
  7. Giữ bí mật là lớp phòng thủ thêm, không phải lớp duy nhất. Thiết kế vẫn phải đứng vững nếu luật bị lộ: cách ly mặc định, cần xác nhận độc lập, crawler làm mốc. Bí mật chỉ làm việc lách tốn công hơn.
  8. +
+ +

Bảng phân chia sở hữu

+
+ + + + + + + + + + +
Thành phầnRepoVì sao
Extension (đọc trang, lời đồng ý, side panel)DOS/Rate (public)Phát cho người dùng, đằng nào cũng đọc được; minh bạch giúp qua duyệt store và tạo niềm tin.
Schema payload ExtensionObservationDOS/Rate (packages/shared-data)Là hợp đồng giữa client và server; client cần nó, Rate Services dùng qua submodule.
Ingest API và kiểm tra cơ bản (định dạng, miền giá trị, host, đồng ý, giới hạn thô)DOS/Rate (apps/web)Chỉ là kiểm hợp lệ, không có luật tin cậy. Không chấm điểm ở đây.
Bảng cách ly (lưu quan sát chưa xác minh)Schema: bản tham chiếu ở DOS/Rate; migration chính thức ở DOS-MeCấu trúc bảng không phải bí mật. Anon chỉ ghi qua RPC hẹp, không đọc được.
Xác minh và chấm điểm tin cậy người gửiDOS/Rate-Services (private)Đây là thứ kẻ tấn công muốn đọc.
Đối chiếu với dữ liệu crawlerDOS/Rate-Services (private)Gắn với crawler, vốn đã private.
Ngưỡng, trọng số, danh sách chặn, giới hạn chi tiếtDOS/Rate-Services (private, cấu hình)Lộ ra là bị gửi vừa dưới ngưỡng.
Đưa quan sát đã xác nhận lên giá công khai (price_history)DOS/Rate-Services (job ghi bằng role worker)Chỉ phía private được ghi giá công khai; web chỉ hiển thị kèm nguồn.
Crawler, proxy, captcha, né botDOS/Rate-Services (private)Như hiện nay, quy tắc cứng của AGENTS.md. Không có phần nào vào extension.
+ +

6. Các phương án

+
+ + + + +
Phương ánMô tảĐượcMất
A. Giữ nguyênTín hiệu cộng đồng chỉ là yêu cầu cào lại; chỉ sửa các điểm lệch ở mục 1.4.Rủi ro thấp nhất, tốn ít công (chỉ phần M1).Không giải quyết tường bot: sàn chặn crawler thì tín hiệu vô dụng.
B. Quan sát thụ động, cách ly, xác minh (khuyến nghị)Extension gửi quan sát tối thiểu khi đã đồng ý; Rate Services xác minh chéo rồi mới công khai; crawler làm mốc.Có dữ liệu ở Shopee, Lazada, TikTok mà không cần né bot; minh bạch; ranh giới repo rõ.Phụ thuộc số người cài; đuôi dài thưa; cần bảng mới (migration DOS-Me), việc ở cả hai repo; rủi ro duyệt store.
C. Crawler phân tán trong extensionServer ra lệnh cho extension tải nền trang người dùng không mở (kiểu Keepa bị báo cáo [4]).Phủ rộng, chủ động.Là logic cào dữ liệu, không được ở DOS/Rate; dùng IP và phiên của người dùng; nguy cơ bị gỡ khỏi store và mất uy tín. Không khuyến nghị.
+

Bổ sung độc lập với cả ba: nguồn chính thức (API affiliate hoặc đối tác của sàn) nếu có, do Rate Services đánh giá riêng.

+ +

7. Khuyến nghị

+
+

Chọn phương án B, triển khai theo mốc, có giai đoạn chạy ẩn (shadow) để đo trước khi bất kỳ quan sát nào lên giá công khai. Sửa ngay các điểm lệch ở mục 1.4 vì chúng là rủi ro duyệt store kể cả khi giữ phương án A. Rate Services sở hữu ingestion phía sau, xác minh và đưa lên giá công khai.

+
+ +

8. Các mốc

+
+ + + + + + +
MốcNội dungRepoĐiều kiện qua mốc
M0JOY quyết định về đề xuất này-JOY trả lời mục 10
M1Sửa lời công bố cho khớp payload, mục extension trong /privacy-policy, CHROMEWEBSTORE.md khớp manifest, thu hẹp host TikTok, màn hình chào opt-inDOS/RateReview fresh-context, kiểm bằng tay trên 10 sàn
M2Schema ExtensionObservation v1, extractor đọc tồn kho và SKU, ingest lưu vào bảng cách ly mới (migration qua DOS-Me, JOY duyệt)DOS/Rate, DOS-MeKhông ghi gì vào giá công khai; test ingest đầy đủ
M3Xác minh và chấm điểm ở chế độ shadow: chấm nhưng không công khai, 2 đến 4 tuầnDOS/Rate-ServicesCác chỉ số ở mục 9 đạt ngưỡng đề xuất
M4Đưa quan sát đã xác nhận lên price_history kèm nguồn; web hiện "nguồn: người dùng, đã đối chiếu"; ý kiến pháp lý; xác nhận Limited Use trong developer dashboard; nộp Chrome Web Store và EdgeCả haiJOY duyệt bước production và bước nộp store
+ +

9. Chỉ số quyết định thành công

+
+ + + + + + + + +
Chỉ sốNgưỡng đề xuất (để JOY chỉnh)
Độ khớp với crawler trên listing cả hai cùng có (shadow)Ít nhất 98% quan sát lệch không quá 1%
Độ khớp giữa người gửi độc lập trên listing crawler không vào được (TikTok, Shopee và Lazada bị chặn), cộng một mẫu kiểm tra bằng tay mỗi tuầnÍt nhất 95% cặp quan sát cùng giờ lệch không quá 1%; mẫu tay khớp từ 95%
Tỷ lệ quan sát đã lên công khai mà sau đó bị chứng minh sai (bị crawler hoặc mẫu kiểm tay bác trong vòng 24 giờ, với giá lệch quá 1%)Dưới 0,5%
Tỷ lệ opt-in trên người dùng hoạt độngĐo trước, đặt ngưỡng sau M2
Phần trăm listing Shopee, Lazada, TikTok trong catalog có giá tươi dưới 24 giờTăng rõ so với chỉ crawler (đo mốc trước M3)
Tuổi trung vị của giá đang hiển thịGiảm so với mốc trước M3
Bị từ chối hoặc gỡ trên store; khiếu nại quyền riêng tư0
+ +

10. Câu hỏi mở cho JOY

+
1. Chọn phương án nào? Khuyến nghị B. A nếu muốn chờ thêm; C không khuyến nghị.
+
2. Mặc định đồng ý: giữ tắt và hỏi rõ ở màn hình chào (khuyến nghị), hay chỉ để công tắc trong side panel như hiện nay?
+
3. Danh tính người gửi: chỉ mã ẩn danh theo lượt cài, hay khuyến khích đăng nhập rate.vn để bản gửi có trọng số cao hơn?
+
4. Listing chỉ một người thấy và crawler không vào được: ẩn tới khi có xác nhận (khuyến nghị), hay hiện với nhãn "chưa xác minh"?
+
5. Ý kiến pháp lý trước khi phát hành rộng: có thuê luật sư không, ngân sách bao nhiêu?
+
6. Phát hành store: dưới tài khoản nhà phát triển nào, và có nộp Edge cùng lúc với Chrome không?
+
7. Giữ số sao và lượt đánh giá trong payload, hay bỏ để payload chỉ còn giá, tồn kho, SKU (khuyến nghị bỏ)?
+ +

Nguồn

+
    +
  1. Honey, "Data and privacy": joinhoney.com/data-and-privacy
  2. +
  3. datarequests.org (2020), về việc Honey thu thập dữ liệu: datarequests.org/blog/honey-data-collection
  4. +
  5. Keepa trên Firefox Add-ons: addons.mozilla.org/en-US/firefox/addon/keepa
  6. +
  7. Wladimir Palant, "Data exfiltration in Keepa Price Tracker" (2021): palant.info
  8. +
  9. Chromium Extensions group, "Keepa extension is deceptively scraping Amazon in the background": groups.google.com.
    [5b] Thư viện Python cho Keepa API: pypi.org/project/keepa
  10. +
  11. Chrome Web Store, User Data FAQ: developer.chrome.com
  12. +
  13. mytour.vn, về tính năng BeeCost: mytour.vn
  14. +
  15. uplus, review extension BeeCost: uplus.substack.com
  16. +
  17. BeeCost trên Ameblo: ameblo.jp/beecost
  18. +
  19. GHN, "BeeCost Shopee là gì": ghn.vn
  20. +
  21. Cổng thông tin Hà Tĩnh, Luật Bảo vệ dữ liệu cá nhân có hiệu lực từ 01/01/2026: hatinh.gov.vn
  22. +
  23. Thư viện Pháp luật, Nghị định 356/2025/NĐ-CP (chưa đối chiếu với Công báo; cần luật sư xác nhận ngày ban hành và hiệu lực): thuvienphapluat.vn
  24. +
+

Mã nguồn đã đọc (DOS/Rate): apps/extension/manifest.json, apps/extension/CHROMEWEBSTORE.md, apps/extension/src/background/service-worker.ts, apps/extension/src/extractors/ecommerce-detector.ts, apps/extension/src/sidepanel/sidepanel.html, apps/web/src/app/api/v1/rate/extension/ingest/route.ts, apps/web/src/server/rate/extension.ts, apps/web/src/app/[locale]/privacy-policy/page.tsx, apps/web/locales/*.json, supabase/migrations/20261018120101_rate_crowd_verify_signal_rpc.sql. DOS/Rate-Services chỉ được đọc để hiểu chung, không trích mã.

+
+ + diff --git a/Docs/Proposals/2026-10-28-extension-data-source.md b/Docs/Proposals/2026-10-28-extension-data-source.md new file mode 100644 index 00000000..c9ab698f --- /dev/null +++ b/Docs/Proposals/2026-10-28-extension-data-source.md @@ -0,0 +1,96 @@ +# Proposal: the browser extension as a primary data source + +- **Date:** 2026-10-28 +- **Issue:** [DOS/Rate#1175](https://github.com/DOS/Rate/issues/1175) +- **Status:** PROPOSAL, awaiting JOY's decisions. No product code has changed. +- **Full decision document (Vietnamese):** [`2026-10-28-extension-data-source.html`](./2026-10-28-extension-data-source.html) + +## Summary + +The private crawlers hit bot walls. TikTok is paused (DOS/Rate-Services#658), Lazada search returns a punish page (DOS/Rate-Services#659), and Shopee works only through one logged-in runner. A real user's browser does not hit these walls, because the user opened the page. + +The recommendation is **Option B**. With consent, the extension sends a minimal observation: price, stock, SKU, listing URL, timestamp and marketplace, and no personal data. The observation goes into a quarantine table. It becomes a public price only after DOS/Rate-Services corroborates it against the crawler or against independent submitters. The crawler stays as the seed and the cross-check. + +The extension never fetches pages the user did not open, never evades bot detection and never solves captchas. + +## Current state (read from `dev`) + +- **Lookup:** on every product page, with no opt-in, the extension sends `GET /api/v1/rate/extension/lookup` with the URL, title, price, item id and shop id. This path is read-only. +- **Ingest:** with the opt-in on (off by default), the extension sends `POST /api/v1/rate/extension/ingest`. The route checks the payload with Zod and allows 15 requests per minute per IP. That limiter is in memory per serverless instance, not global. It then calls the `rate.enqueue_crowd_verify_signal` RPC, which enqueues an `extension_crowd_verify` job in `rate.crawl_jobs`. The RPC caps all crowd jobs at 120 per minute; this is the only global limit. +- **Quarantine today:** from a read of the Rate-Services code, the worker has no separate handler for this job type and re-crawls the URL like any other job. The price the user saw is not used as data, so the crowd signal does not help when the crawler is blocked. +- **Mismatches to fix under any option:** + - The opt-in label says only the cleaned URL is sent. The payload actually has ten fields: platform, item id, shop id, URL, title, variant, price, original price, rating and rating count. + - `CHROMEWEBSTORE.md` justifies `tabs` and `localhost`, which the manifest does not request. It omits five hosts that the manifest does request: TikTok, Hoang Ha, Di Dong Viet, Phong Vu and `www.rate.vn`. Its privacy section says the extension "only activates" on product pages of 6 domains, while the content script matches every path on 10 sites. + - The default lookup sends browsing data without opt-in. That is probably allowed for a user-facing feature if it is disclosed, but this is an assumption and belongs in the legal review. + - `*.tiktok.com` is broader than product pages need. + - `/privacy-policy` does not mention the extension. + - The extractor does not read stock. + +## Comparable products (public sources only) + +- **Honey:** Honey says it collects general product data such as price and availability, only on supported stores, to power price history and Droplist ([joinhoney.com](https://www.joinhoney.com/data-and-privacy/)). datarequests.org says Honey collects more browsing data than its policy states ([datarequests.org](https://datarequests.org/blog/honey-data-collection)). +- **Keepa:** Keepa says its add-on sends the ASIN of the product the user is viewing ([AMO](https://addons.mozilla.org/en-US/firefox/addon/keepa/)). In 2021, Wladimir Palant reported that the server instructs the extension to load, in the background, Amazon pages the user never opened ([palant.info](https://palant.info/2021/08/02/data-exfiltration-in-keepa-price-tracker/), [Chromium group](https://groups.google.com/a/chromium.org/g/chromium-extensions/c/il9VqQBc8So)). Rate must not do this. +- **BeeCost:** BeeCost has extensions for Chrome and Coc Coc that show price history for Shopee, Tiki, Lazada and Sendo ([mytour.vn](https://mytour.vn/en/blog/bai-viet/top-5-features-of-beecost-the-ultimate-tool-for-finding-the-best-online-shopping-deals-mytour.html)). The developer claims over 100 million products ([ameblo.jp](https://ameblo.jp/beecost/)). No public document describes how it collects data. + +## Anti-poisoning, in short + +- Every submission is untrusted, because the client is open and the API can be called directly. +- The server issues `submitter_id` when the user consents and records the first-seen time itself. A new id starts with low trust, so rotating ids gains nothing. The per-id limit only bounds honest clients. +- The raw IP is not stored. Rate-Services keeps a short-lived, salted hash of the network range (for example /24 or ASN) to test independence. That hash is personal data and is disclosed. +- Observations stay quarantined until they match the crawler or enough independent, trusted submitters. + +## Why the extension is public while crawling is private + +The extension is not a crawler. It ships to every user and can be unpacked, so a private repo would not hide it. Being open lets users and store reviewers check that it does what it says. + +The sensitive part is how the server decides which submissions to trust. An attacker would call the API directly and would want to know the thresholds. That logic lives in DOS/Rate-Services. Secrecy is defense in depth only: the design must still hold if the rules leak, through default quarantine, independent corroboration and the crawler baseline. + +| Piece | Repo | +| :----------------------------------------------------------------------------- | :------------------------------------------------------- | +| Extension: page reading, consent UI, side panel | DOS/Rate (public) | +| Payload schema `ExtensionObservation` | DOS/Rate, `packages/shared-data` | +| Ingest API and basic validation (format, ranges, host, consent, coarse limits) | DOS/Rate, `apps/web` | +| Quarantine table | Reference copy in DOS/Rate; official migration in DOS-Me | +| Verification and submitter trust scoring | DOS/Rate-Services (private) | +| Crawler cross-check | DOS/Rate-Services (private) | +| Thresholds, weights, blocklists, detailed limits | DOS/Rate-Services (private config) | +| Promotion of observations to public `price_history` | DOS/Rate-Services (worker role) | +| Crawlers, proxy, captcha, bot evasion | DOS/Rate-Services (private); never in the extension | + +## Options + +| Option | Description | Verdict | +| :------------------------------------------------------ | :------------------------------------------------------------ | :------------------------------------------------------------------------------------------------ | +| A. Keep as is | Fix the mismatches only. | Safe, but does not solve the bot walls. | +| B. Passive observation with quarantine and verification | Described above. | **Recommended.** | +| C. Distributed crawler in the extension | The server tells extensions to fetch pages in the background. | Rejected. This is scraper logic, it misuses users' IPs and sessions, and it risks store takedown. | + +An official source (a marketplace affiliate or partner API), where one exists, can complement any option. Rate-Services would assess it separately. + +## Milestones + +| Milestone | Work | Repo | +| :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------- | +| M0 | JOY decides. | - | +| M1 | Fix the disclosure copy, the privacy policy, `CHROMEWEBSTORE.md` drift and the TikTok host scope. Add an onboarding opt-in screen. | DOS/Rate | +| M2 | Add the `ExtensionObservation v1` schema. The extractor reads stock and SKU. Ingest writes to a new quarantine table through a DOS-Me migration, with JOY's approval. Nothing is published. | DOS/Rate, DOS-Me | +| M3 | Run verification in shadow mode for 2 to 4 weeks: score observations but publish nothing. | DOS/Rate-Services | +| M4 | Promote corroborated observations with provenance. Get a legal opinion. Certify Limited Use in the developer dashboard. Submit to the Chrome Web Store and Edge Add-ons. | Both | + +**Proposed success thresholds:** + +- On listings both sources cover, at least 98% of shadow observations within 1% of the crawler. +- On listings the crawler cannot reach (TikTok, blocked Shopee and Lazada), at least 95% of same-hour pairs from independent submitters within 1%, plus a weekly manual spot-check sample. +- Under 0.5% of promoted observations later proven wrong, meaning the crawler or a manual check contradicts them by more than 1% within 24 hours. +- Better price freshness on Shopee, Lazada and TikTok than with the crawler alone. +- Zero store rejections and zero privacy complaints. + +## Open questions for JOY + +1. Which option: A, B or C? +2. Should the opt-in stay a side-panel toggle, or also be asked on an onboarding screen? +3. Should submitters stay anonymous per install, or should a rate.vn sign-in add weight? +4. Should a listing seen by one user only, which the crawler cannot reach, stay hidden or show as unverified? +5. Should we get a legal opinion before wide release, and with what budget? +6. Which store developer account should publish, and should Edge ship together with Chrome? +7. Should ratings stay in the payload?