Extension privacy page: what each claim rests on
The Chrome Web Store listing of the BoostEcom Spy extension points to /legal/privacy/extension (src/app/(marketing)/legal/privacy/extension/page.tsx). The Web Store reads that page against the boxes ticked in its…
The Chrome Web Store listing of the BoostEcom Spy extension points to
/legal/privacy/extension
(src/app/(marketing)/legal/privacy/extension/page.tsx). The Web Store reads
that page against the boxes ticked in its dashboard, so every sentence has to
be true of the code. This file is the receipt: one row per claim, with the
place that proves it, then the places where the extension dossier
(store/*.md in the extension repository) disagrees with the code.
Target behaviour (the page is ahead of the extension)
The page describes the behaviour the extension is being changed to, not the
behaviour of manifest 1.0.6 as audited. Rows below marked target rest on
the extension release that is still to be merged; their proofs are the
"Target pins" in src/test/legal-extension-matches-the-manifest.test.ts
(skipped until BOOSTECOM_EXTENSION_TARGET=1). The target, in one list:
- Account required. Signed out, the extension shows only the sign-in
screen and sends nothing about the page. The only request it makes is the
session check (
/api/usage,/api/me), which carries no website address. - Nothing about a page is read or sent without a click. The click is the toolbar icon, or Auto-open (off by default), which counts as that click. After it, the store's domain is looked up on BoostEcom's servers; if the platform does not know it yet, the extension asks BoostEcom to add it to its index (domain only, signed in), and BoostEcom's servers collect it in the background. Nothing read on the page is sent.
- The only thing that runs on page load is local platform detection (Shopify, Webflow, Framer, Next.js and others), with no network request. It sets the platform logo on the toolbar icon.
- The native side panel, while open, follows the user's navigation: local detection first, and only when a supported platform is detected is the domain looked up on BoostEcom. Closed, it does nothing.
- No Meta. The browser-side Ads Library read is removed (no
facebook.comAds Library request, no per-store result cacheskp_ads_probe:*). Ads data comes only from BoostEcom's servers. - Google favicons only for app and pixel vendor domains from the fixed table. Similar stores get no request to Google: their tile shows a bundled initial or glyph.
What the extension team must change to make 1 to 6 true is listed in
docs/architecture/ui-consistency-contract.md (section "Extension changes
required by the privacy page"). What to re-pin in the tests once it merges:
| Test | Re-pin |
|---|---|
legal-extension-matches-the-manifest.test.ts target pins | Run with BOOSTECOM_EXTENSION_TARGET=1; once green, make them unconditional. |
| same file, "asks to add an unknown storefront once a day, by domain only" | The panel-side shape is loose on purpose (unknown: true, message shape); tighten to the new call sites and the account guard. |
| same file, "draws vendor icons from a fixed table" | referrerPolicy = "no-referrer" floor is 2 (brand mark, header icon); set the exact count. |
same file, EXTENSION_STORAGE_PENDING_REMOVAL | Empty the list (skp_ads_probe) once the key is gone from the source. |
same file, AUDITED_MANIFEST.version and EXTENSION_AUDITED_VERSION | Bump together if the manifest version moves; re-read the code first. |
| This file | Replace the ext: line references of the rows marked target with function names of the merged commit. |
How to read the references
ext:is the extension repository, commit3c88bf0(manifest1.0.5). Line numbers are taken from that commit, not from a working tree: the extension moves daily, andsidebar/sidebar.jshad uncommitted edits during the audit. Re-read the code before you trust a line number; the function names are the stable part.pf:is this repository, base391931573.- Rows added for extension
1.0.6(commit0976926of the extension repository, branchPrivate/Extensions/@BoostEcom/Development, published to the Web Store after the1.0.5audit) name functions only:uninstallFeedbackUrl,registerUninstallUrl,requestBroadHosts. No permission, host or route changed between1.0.5and1.0.6. - Rows added for extension checkout
cd6d601(after the 1.0.5 audit) name functions only, never line numbers:ui/workbench.jsbytesOf,mediaUrl,download,catalogCsv,ui/index.jsappLogoUrl,brandFaviconUrl,createBrandMark. The addendumstore/PRIVACY-ADDENDUM.md("Vérification" table, rows 1d2, 1d3, 3c, 3g) states the same proofs. - A claim marked caveat is true with the nuance written in the page. A claim marked platform is verified on this side only. Not verifiable is listed again in the TODO table at the end.
What keeps this true
| Guard | What it does |
|---|---|
src/test/legal-extension-matches-the-manifest.test.ts | The permission, host and version lists of _registry/extension.ts equal an audited snapshot of the 1.0.5 manifest, always. When the extension repository is checked out next to this one (BOOSTECOM_EXTENSION_DIR, /home/user/Ecosystem, or ../Ecosystem) it also compares with the real manifest.json, searches the source for every route, storage key and third-party host the page names, fails if the extension ever calls /api/intelligence/panel/ingest, and checks that cookies are omitted where the page says so. Storage is exact: it scans every script of the extension for skp_* names and fails on a key the registry does not list (EXTENSION_STORAGE) unless it is a declared non-write (EXTENSION_STORAGE_NOT_WRITTEN), and fails if a key of an older version (EXTENSION_STORAGE_REMOVED) is used other than in a remove(). It also pins the media limits (EXTENSION_LIMITS), the credentials rule of the media fetch, the catalog CSV request, the Wayback tab and the fixed vendor table of the icons. |
src/test/legal-extension-catalogue.test.ts | Six locales have the same keys, placeholders and tags; every permission, request flow and third-party recipient of the registry has its sentence; the Limited Use sentence is the Store's verbatim in all six; every route named exists here; the media, catalog CSV, site history and icon sentences carry their limits, URL shapes and trigger in all six. |
src/app/(marketing)/legal/privacy/extension/page.test.ts | Renders the real page in six locales with the real translator (it throws on a bad message). |
src/test/legal-documents-are-routed.test.ts | Page, registry entry, sitemap row and translations all exist; it now walks nested folders. |
To change the page for a new extension version: re-read the code, update this
file, update AUDITED_MANIFEST in the test and EXTENSION_AUDITED_VERSION in
the registry in one commit.
Claims, with proof
Not collected
| Claim on the page | Proof in the extension | Proof on the platform |
|---|---|---|
| No browsing history is kept or sent. The only domains sent are the page where the user clicks the toolbar icon (or where Auto-open opens the panel), the pages navigated to while the native side panel is open and a supported platform is detected there (target), and the stores the user asks to scan or track. | The only requests to boostecom.app are in the "sent" table below. Every one passes normalizeDomain (ext:background/index.js:903: strips protocol, path, www.). No call to /api/intelligence/panel/ingest anywhere in background/, sidebar/, ui/, options/, popup/, sidepanel/ (asserted by the test). Probe results live in a Map (:290); store records are cached in storage.session for 10 min, 50 domains (:2319, :2338). | The read routes keep no per-user domain list: pf:src/app/api/intelligence/lookup/route.ts:170,224, graph/[domain]/route.ts:63, seo/[domain]/route.ts:69, predict/[domain]/route.ts:66, supplier/[domain]/route.ts:53, similar/route.ts:79 only findUnique/findMany on storeIntelligence. Caveat, platform: POST /api/intelligence/panel/ingest and the table IntelligencePanelEvent (pf:prisma/schema.prisma:6499) exist to store visit events per panelist pseudonym. Dormant (pf:src/app/api/cron/intelligence/prune-history/route.ts:18 says so). See the risk section. |
| Page content is not collected except for the store the user analyses; pick / capture / observation act on the current page on request and stay in the browser unless saved to the account or attached to a conversation. | Pick: ext:sidebar/sidebar.js:8411 (take), payload built :8487-8530 (selector, HTML capped 5000 at :8481, text capped 1200 at :8492). Capture: captureFullPage ext:background/index.js:4497, context.text capped 12 000 at :4858. Observation: ext:ui/evidence.js:46 (collect) runs only from the "Capture / recheck" button (ui/research.js). Hand-off to an open boostecom.app tab by postMessage: forwardToPlatformTab :81, forwardPageToPlatformTab :110, call sites :3869, :3890, :3909, :4323, :4972. | The page-side consumer only re-dispatches a DOM event, pf:src/features/ai/chat/runtime/use-chat-platform-bridge.ts:84-103, which becomes a composer attachment (pf:src/features/ai/chat/runtime/use-chat-attachments.ts:513,599). Nothing is posted until the user sends the message. |
| No keystrokes, clicks or mouse movements recorded. | The only keydown listener is the picker's Escape handler (ext:sidebar/sidebar.js:8575-8579, registered :8602). grep finds no keypress/keyup, no sendBeacon, no WebSocket. Pointer listeners exist only while the picker is active (:8600-8602). | n/a |
| No cookies of other sites; the extension never reads cookies. | ext:manifest.json:26-34 has no cookies permission; no document.cookie or chrome.cookies in the source. Session cookie only to boostecom.app: fetchIntelJson ext:background/index.js:1101-1114 (include iff hostname is boostecom.app, else omit); the robots request ext:ui/page-context.js:323 and media download ext:ui/workbench.js:84 use credentials: 'omit'; /products.json goes through fetchIntelJson (:1822). | n/a |
| No passwords or payment details; tokens kept, not the password. | OAuth 2.1 + PKCE: ensureOAuthClient :2772, token exchange :2877, tokens in storage.local under skp_oauth_tokens. No card or password field anywhere. | OAuth routes pf:src/app/api/oauth/*. Tokens of the user are erased by deleteUserCompletely (pf:src/app/api/auth/delete-account/route.ts, tables listed in its header). |
| No geolocation; the server sees the IP like any request. | No geolocation use. | pf:src/app/api/extension/uninstall-feedback/route.ts:56 and pf:src/lib/hub/read-budget.ts:133 key limits on IP / account. |
| No advertising and no analytics library. | No third-party script in manifest.json, CSP script-src 'self' (ext:manifest.json:143); tracker names in ext:ui/page-report.js:13 and ext:sidebar/sidebar.js:982 are detection patterns for the inspected page, not loaded libraries. | n/a |
Stays on the device
| Claim | Proof |
|---|---|
| Page diagnostics are read locally. | probeTab ext:background/index.js:315 (MAIN world, returns to the worker, cached in probeCache :290); ext:ui/page-report.js, ext:ui/page-context.js make no platform call. |
| Platform detection (Shopify, Webflow, Framer, Next.js and others) runs on page load wherever the extension has host access, locally and with no network request, and sets the logo on the toolbar icon (target: the list of platforms is wider than Shopify); kept in memory. | tabs.onUpdated -> syncTab on complete for any tab (ext:background/index.js:3554), ensureProbe :3150 requires hasHostAccess. Caveat: with the optional all-sites access this is every page you load. |
| Robots header request: HEAD then GET, no cookies, result stays in the panel. | ext:ui/page-context.js:323 (robotsHeader). |
The panel reads the store's public /products.json and /collections.json from the store page itself, without cookies, once per store per browser session (cached in storage.session, 20 stores, 6 hours); the result stays in the panel and is never sent to BoostEcom. Separate from the CSV below. | ext:ui/page-context.js readStorefront (credentials: 'omit', same origin), run in the tab by storefrontRead (ext:background/index.js, key skp_storefront_read); worker fallback fetchStorefrontCatalog -> fetchIntelJson (omit for non-boostecom hosts). Key listed in EXTENSION_STORAGE. |
| Screenshots are made by the browser and stay local until handed over. | captureVisibleTab ext:background/index.js:3854 (Page tab preview, ext:sidebar/sidebar.js:6723), captureFullPage :4497. |
Page media download: click only (hover button, media list, ZIP); fetch with credentials omitted cross-origin and same-origin otherwise; http(s), data: and blob: image or video only, HTML refused; 200 MB single, 50 MB per file in a ZIP, 150 MB per ZIP, 300 listed; saved through the browser, nothing sent to BoostEcom or a third party; a host that blocks CORS opens the file in a new tab. | ext:ui/workbench.js: bytesOf (credentials: sameOrigin(url) ? 'same-origin' : 'omit', cache: 'force-cache'), mediaUrl, MAX_FILE, MAX_ZIP_FILE, MAX_ZIP, MAX_ITEMS, saveBlob / saveLink, download (TypeError -> window.open(url, '_blank', 'noopener,noreferrer')). No call to the platform. The test pins the constants against EXTENSION_LIMITS. |
Catalog CSV: click only, Shopify pages only, {page origin}/products.json?limit=250&page=N with N up to 10, credentials omitted, built and saved locally. | ext:ui/workbench.js catalogCsv (credentials:'omit', loop page<=10, downloadBlob); the row is mounted only when ctx.isShopify === true. Pinned by the test against EXTENSION_LIMITS. |
The on/off choice of the media hover button is kept in storage.local (skp_media_overlay, a boolean). | ext:ui/workbench.js PREF, setPref; listed in EXTENSION_STORAGE. |
| Library: saved sites / ads, folders, observations, briefs, colors, ads-spend rate in the browser. | ext:ui/evidence.js:12 (cleanDocument, bounds 500/30/50/50, 700 000 bytes); keys skp_saved_v1 (background/workspace.js), skp_ads_cpm (ui/index.js), skp_recent_colors (ui/workbench.js:53). |
| Theme editor structure snapshot kept locally. | ext:sidebar/sidebar.js:9921 (skp_theme_snapshot), written :10042. Not in the dossier. |
Sent to BoostEcom
| Flow (page wording) | Extension | Platform |
|---|---|---|
| Store record (target): domain of the page when the signed-in user clicks the toolbar icon, and, while the native side panel is open, when a supported platform is detected on the page navigated to; session cookie. Signed out: nothing. | loadStoreIntel :2583 (q=<domain> :2614), readIntel :1175 (seo / predict / supplier), storeGraph :1294, storeSimilar :1768; cookie rule :1112. | Read-only routes listed above. |
| Auto-open: off by default; counts as the toolbar click for each page it opens on; signed in only (target: no Shopify-only condition, any supported platform). | maybeAutoOpen ext:background/index.js:3186-3205 (skp_auto_open !== true -> return; isConnected; isShopify). Toggle in ext:options/options.js:176-200 (also requests the optional hosts). | n/a |
| Adding a store BoostEcom does not know (target): after the click, when the platform does not know the store yet (or knows it without traffic figures), the extension asks BoostEcom to add or refresh it, signed in only, once per store per day (daily slot, cooldown and rate limit apply), domain only, nothing read on the page is sent; BoostEcom's servers collect it in the background. Signed out: nothing. | ext:sidebar/sidebar.js:3862 (ask({ seed: true, refresh: true }), guarded by connected, visitsMissing, _refreshTried); once per store per day: refreshedRecently :2445, INTEL_REFRESH_TTL_MS :2439; body { domain } :2670. | pf:src/app/api/intelligence/hub/scan/route.ts: crawl needs a user or a solved Turnstile (mayCrawl, :326); the scan runs with actor.userId: null (:534). Unknown-storefront case (checkout cd6d601, function names): ext:sidebar/sidebar.js ask({ seed: true, refresh: true, unknown: true }), run only when !guest && connected; ext:background/index.js storeIntel (unknown branch), seededRecently, markSeeded, refreshStoreIntel. The test asserts the sign-in guard, the one-per-day marks and the route. |
| Scan this store (store not indexed): domain, user click. | CTA ext:sidebar/sidebar.js:6363 -> relaunch -> ask({ seed: true }). | Same route. Adds a count to a weekly per-domain tally with no user (pf:src/services/algorithms/intelligence/refresh-tiers.ts:95, called at scan/route.ts:441,580,605); the record may enter the Intelligence index, from which a storefront's owner can opt out (/intelligence/transparency). |
Sign-in check: /api/usage then /api/me; keeps first name, flag, plan, credits, store index. | checkSession ext:background/index.js:2968, :2994, :3070, cacheStoreIndex :988; triggers onStartup :3492, onInstalled, boostecom tab load :3506-3509, panel open. | withSessionAuth routes. |
| Platform status: no cookies. | appStatus ext:background/index.js:4921 (credentials: "omit"); triggered by the Help tab, ext:sidebar/sidebar.js:1997. | pf:src/app/api/status/summary. |
| Connect a store: OAuth client registration, tokens kept locally, revoke on disconnect. | :2772, :2877, disconnectPlatform :2913 -> :2920. | pf:src/app/api/oauth/*, pf:src/app/oauth/authorize. |
| Cloud library / brief / alerts, via an open boostecom.app tab and the session. | ext:ui/research.js:33 (library PUT of the whole document), :85 (brief), :93 (watch / pulse); ext:background/workspace.js:24 (relay, executeScript in the platform tab, credentials: 'same-origin'). The document holds full URLs, titles, observed fields (ext:ui/evidence.js:12-24). | pf:src/app/api/extension/library/route.ts:19-26 (700 000 bytes, owner + revision check, stored on User.extensionLibrary, pf:prisma/schema.prisma:2274), brief/route.ts:20, watch/route.ts, pf:src/app/api/intelligence/pulse. |
| Track a store: domain + destination store id. | SKP_TRACKER ext:background/index.js:3618. | pf:src/app/api/intelligence/hub/tracker/route.ts:112. |
Uninstall: Chrome opens the page with ?v= and ?lang= only. From 1.0.6 the language is the one set in the extension (skp_lang, not "auto"), otherwise the browser's (uninstallFeedbackUrl(prefLang), registerUninstallUrl, and a storage.onChanged refresh). | UNINSTALL_URL_BASE :3986, uninstallFeedbackUrl :3988 (regex-validated version and 2-3 letter language), setUninstallURL :4018. | Page pf:src/app/(minimal)/extension/uninstalled/page.tsx (noindex); survey posts from uninstall-survey.tsx:65; route uninstall-feedback/route.ts: IP required and limited to 5 per hour (:45-56), userId: null, email: null (:99), comment <= 500 (uninstall-feedback.ts:35), admin email (:114). The IP is only a rate-limit key, not a column. |
Sent to other companies (browser to company, not via BoostEcom)
| Claim | Proof |
|---|---|
Google Search on click; query = selected text (<= 30 words) plus site:<host> for the "on this site" variant. | findTextSearchUrl ext:background/index.js:4198, FIND_TEXT_MAX_WORDS :4175; menu built in writeContextMenus :4098; opens only from contextMenus.onClicked. |
Google Lens on click; image address only, q=aliexpress for the supplier search. | imageSearchUrl ext:background/index.js:4218. |
No Meta recipient (target): the extension never contacts Meta. The browser-side Ads Library read (runAdsLibraryProbe, the optional facebook.com permission request, the skp_ads_probe:* cache) is removed; ads figures come from BoostEcom's servers. | Pinned by the target pins (no facebook.com/ads/library, no runAdsLibraryProbe, no skp_ads_probe). Until the merge the dev extension still carries it, tracked by EXTENSION_STORAGE_PENDING_REMOVAL. |
| Icons of apps and pixels without a bundled brand mark come from Google's favicon service for the vendor's domain taken from a fixed table (never the visited page's domain; an app missing from the table gets a neutral icon and no request), requested with no referrer. Target: similar stores get no request to Google (their tile shows a bundled initial or glyph); the visited site's own icon is read from the page. Google sees the IP and the vendor domain. Bundled brand marks make no request. | ext:ui/index.js: APP_DOMAINS (table), appLogoUrl and brandFaviconUrl (table only, else null), createBrandMark (inline brandMark first, img.referrerPolicy = "no-referrer"), the header icon. Target pin: ext:background/index.js storeSimilar no longer builds a s2/favicons URL from the store's own domain. |
Site history opens https://web.archive.org/web/*/{hostname} in a new tab on click; the extension itself sends no request to archive.org. | ext:ui/workbench.js row toolWayback: window.open(...,'_blank','noopener,noreferrer'). The test asserts no fetch towards archive.org and no downloads permission. |
| Product, ad and thumbnail images load from the addresses in the store record. | ext:ui/index.js createStoreShot :3370, product strip (image_url, :3710), creative cells. Caveat: differs from the dossier, see D4. |
| Flags are bundled. | SKPUI.flagUrl ext:ui/index.js:431 (chrome.runtime.getURL). |
Choices
| Claim | Proof |
|---|---|
| The media hover button can be turned off in the Tools tab; downloads, the catalog CSV and the site history happen only on a click. | ext:ui/workbench.js: toggle row toolMediaButtons -> setPref writes skp_media_overlay; catalogCsv, download and the toolWayback row run from a click handler only. |
| Auto-open, the right-click menu and the optional site access stay as listed on the page. | Unchanged (maybeAutoOpen, writeContextMenus, permissions.request). |
The optional site access is asked for by two settings, Auto-open and "Platform logo on the toolbar for all sites" (skp_badge_all_sites, off by default); it is removed only when neither is on. | ext:options/options.js (both toggles call permissions.request), ext:background/index.js (SKP_BADGE_ALL_SITES, requestBroadHosts). |
| Signed out, the panel shows the sign-in card only; the right-click menu and the keyboard shortcuts do not check the session and keep working on the device. | ext:background/index.js (contextMenus.onClicked, commands.onCommand) call no session gate; only analyzeAndOpen does. |
Permissions
manifest.json:26-34 lists storage, scripting, identity, contextMenus, clipboardWrite, activeTab, sidePanel; :35-42 the hosts (the page drops
http://localhost:3000/*, which the extension repository's packaging script (zip-cws.sh) removes from the
package); :43-46 the optional hosts. Evidence of use: storage everywhere;
scripting probeTab :315; identity launchWebAuthFlow :2849;
contextMenus :4098; clipboardWrite navigator.clipboard :4273,:4406;
activeTab toolbar click chrome.action.onClicked :3969; sidePanel
ext:background/workspace.js:46 and sidepanel/. The page's plain-language
wording matches store/CWS-DASHBOARD.md section 2.
Retention and deletion
| Claim | Proof |
|---|---|
| Uninstalling deletes the browser-side data. | Chrome behaviour for extension storage. |
| Account deletion erases the cloud library, briefs and OAuth tokens. | deleteUserCompletely (pf:src/services/organizations/delete-user.ts); User.extensionLibrary is a column of the deleted row; retentionRow("account"). |
| Uninstall answer carries no account; IP only for a 1 h rate limit. | uninstall-feedback/route.ts:45-56,99; the rate-limit key is extension-uninstall:ip:<ip> over RATE_WINDOW_MS = 1 h. |
Where the extension dossier disagrees with the code
Report only: the extension repository was not modified. "Fix" says what to
change in store/*.md or in the code.
| # | Dossier says | Code does | Fix |
|---|---|---|---|
| D1 | Resolved by the target (the browser-side Ads Library read is removed). Was: store/LISTING.md:138 and :224, PRIVACY-ADDENDUM.md section 3: the live ads count and right-click searches contact Meta or Google "only when you click". | The Meta permission is requested from a click, but once granted the Ads Library is read automatically on every store opened whose record has no ad count (ext:sidebar/sidebar.js:4570-4621, silent: true). The reviewers' note in CWS-DASHBOARD.md section 2 does say "reused for later readings"; the listing and the addendum do not. | Reword the listing and the addendum. The public page already says it. |
| D2 | Resolved by the target (no Meta request at all). Was: addendum section 3: Meta "receives the request as it would if you opened the Ads Library yourself". | The query carries the visited store's domain (q="<domain>", ext:background/index.js:2119+) and the store's Facebook page, in a tab that uses the user's Facebook session. This is a transfer of visited-store domains to Meta, not covered by the "Web history" reasoning in CWS-DASHBOARD.md section 4, which only names boostecom.app. | Add Meta to the Web-history rationale; keep the box ticked. |
| D3 | Addendum section 3: Google Search tab "containing that text". | The "on this site" query is site:<host of the current page> "<text>" (:4198). | Mention the host. |
| D4 | CWS-DASHBOARD.md section 3: "Only optional images (favicons, flags) are loaded from the web"; addendum section 3 names only favicons. | Product pictures, ad creatives and storefront thumbnails are <img> loads from addresses in the store record (ext:ui/index.js:3370,3710, creative cells), so those hosts see the user's IP. (Flags are bundled, as stated.) | Say so in section 3 and in the remote-code note. |
| D5 | Addendum section 4: "local extension storage: ... cached store records and plan information". | Store records are cached in storage.session (10 min, <= 50 domains, ext:background/index.js:2319,2338). storage.local also holds skp_stores (names and domains of the user's stores, :988), skp_billing, skp_theme_snapshot. | Correct the list. |
| D6 | CWS-DASHBOARD.md section 4 (Web history row) and addendum section 2: the extension sends "neither the full URL nor the page title". | True for the domain lookups. False for the capture hand-off and the cloud library: a full-page capture carries location.href (query string included), title, description, headings, up to 12 000 characters of visible text and screenshot tiles (ext:background/index.js:4858-4885), relayed to every open boostecom.app tab (:110), and the library push carries full URLs, titles and observed fields (ext:ui/evidence.js:12-24). | Scope the sentence to "the domain lookups". The "Website content" box is already ticked. |
| D7 | Not mentioned. | lastSelectedElement (selector, HTML, text, attributes including input value) and lastFullPageCapture (everything but the images, including the page text and full URL) are written to storage.local and never read back (ext:background/index.js:3884,3901; a grep finds no reader). They persist until the next capture or uninstall. | Remove both writes (privacy by default), or clear after relay. The public page discloses them as stored. |
| D8 | Addendum section 2 lists the daily collection but not the "Scan this store" flow. | A signed-in or anonymous user can request a scan of an unindexed store (ext:sidebar/sidebar.js:6363); the platform may add it to the Intelligence index (pf:.../hub/scan/route.ts). | Add the flow to the addendum. |
| D9 | Addendum section 2: the sign-in check asks for "your first name, plan and credits". | GET /api/usage and GET /api/me; /api/me returns the organizations, stores and connector metadata, and the extension caches an index of store names and domains (cacheStoreIndex :988). | Mention the store index. |
| D10 | Addendum section 2: "sending an element you picked to an open boostecom.app tab". | Full-page captures are relayed too, to all open boostecom.app tabs (forwardPageToPlatformTab :110). | Mention captures. |
| D11 | Addendum section 5 and its "to confirm" list: the survey page was "not present in this repository". | Confirmed on the platform: userId: null, email: null, 5 answers per IP per hour, comment <= 500 characters. Not in the dossier: every answer sends an email to an administrator (uninstall-feedback/route.ts:114). | Mention the admin email. |
| D12 | store/LISTING.md:53,330,408 and CWS-DASHBOARD.md section 6: the policy URL is /legal/privacy, "which must contain the addendum text". | The text now lives at /legal/privacy/extension, linked from the "Data processed by BoostEcom Spy" section of /legal/privacy. | Set the dashboard field to https://www.boostecom.app/legal/privacy/extension (or keep /legal/privacy, one click further for a reviewer). |
| D13 | Resolved on the page, and tightened by the target (similar stores no longer ask Google). Was: addendum section 3: favicons "see ... the domain requested". | The domains asked are those of the apps a store uses and of the similar stores returned by the platform, not the domain being visited (ext:background/index.js:1802, ext:ui/index.js:4153). The visited site's own favicon is read from the page (ext:sidebar/sidebar.js:8232), no third party. | Say "apps and similar stores". |
| D14 | AGENT.md (extension, security section): "La collecte de visites (/api/intelligence/panel/ingest) exige la session". pf:docs/architecture/panel-ingest-contract.md: the extension "collecte les evenements de navigation cote utilisateur (avec consent)". | Version 1.0.5 never calls that route. Both documents describe a feature that is not shipped. | Mark them as planned, or delete the route (risk below). |
Risk to decide: the dormant visit-ingest route
pf:src/app/api/intelligence/panel/ingest/route.ts accepts batches of
{ primary_domain, visited_at, referer, country } from a signed-in extension
and stores them per panelist pseudonym (IntelligencePanelEvent, retained
180 days by prune-history). Nothing in extension 1.0.5 calls it, the public
page says the extension keeps and sends no browsing history, and the test
fails if the extension ever does. If panel collection ships later,
that is a new data type: new consent screen, new Web Store data-usage
answers (Web history is already ticked, but the purpose changes from "show the
record" to "build a traffic panel", which the Limited Use rule on web
browsing activity forbids unless it is the prominently disclosed user-facing
feature), a policy change before release. Until then the route is an
unjustified write surface; consider disabling it.
Facts the public page does not state (open points)
The page deliberately leaves these out because no source in either repository states them. Nothing here is invented in the public text.
| Missing fact | Why it matters | Where it would go |
|---|---|---|
| Retention of hosting request logs (IP, time, path with the looked-up domain). | The page says logs exist and defers to the privacy policy, whose "Browsing data" line gives no period. | Retention table, _registry/retention.ts, with a guard. |
Retention of uninstall answers (bst_feedback, topic extension_uninstall). No cron prunes them. | GDPR retention duty if a comment contains personal data. | Retention table. |
| Retention of Intelligence index records created by a user scan (opt-out only). | The existing policy relies on the opt-out. | Privacy policy, "Storefronts we analyze that are not yours". |
Server-side lifetime of the extension's OAuth tokens (the extension assumes 30 days for refresh, ext:background/index.js refreshExpiresAt; the server decides). | A reviewer may ask. | This page, "Connecting a store". |
Whether a DPO is required or designated. The existing policy names a controller and contact@boostecom.app only. | Contact line. | Privacy policy, controller section. |
| Whether Google and Meta, which receive requests from the user's browser, need a mention in the DPA or sub-processor list. The page treats them as independent recipients. | Counsel question. | _registry/subprocessors.ts or a note on that page. |
| Wording check of the Limited Use sentence. It is the Store's own example sentence (it mentions "Google APIs" although the extension uses none); the surrounding commitments are the four requirements of the policy. | Keep verbatim for the review. | n/a |
| Extension UI is available in English and French only, the policy in six languages. | Informational. | n/a |