Audience: Developers, security reviewers, and privacy-conscious users wanting to inspect what this extension does.
| Permission | Scope | Justification |
|---|---|---|
storage |
chrome.storage.local and chrome.storage.session |
Cache product classifications; store per-tab NOVA state |
Host: https://www.tesco.com/* |
Content script injection | Content script scoped to product detail pages only; required to read product DOM and inject NOVA badges |
Host: https://www.sainsburys.co.uk/* |
Content script injection | Content script scoped to product detail pages only; required to read product DOM and inject NOVA badges |
Host: https://www.asda.com/* |
Content script injection | Content script scoped to product detail pages only; required to read product DOM and inject NOVA badges |
Host: https://groceries.morrisons.com/* |
Content script injection | Content script scoped to product detail pages only; required to read product DOM and inject NOVA badges |
Host: https://www.waitrose.com/* |
Content script injection | Content script scoped to product detail pages only; required to read product DOM and inject NOVA badges |
Host: https://www.ocado.com/* |
Content script injection | Content script scoped to product detail pages only; required to read product DOM and inject NOVA badges |
Host: https://world.openfoodfacts.org/* |
Fetch calls from service worker | Look up NOVA scores and ingredient classifications |
The extension deliberately does not request:
tabs— cannot read your tab list or browsing historyhistory— cannot access browser historycookies— cannot read or set cookieswebRequest/declarativeNetRequest— cannot intercept network trafficclipboardRead/clipboardWrite— cannot access clipboardgeolocation— cannot access locationidentity— no user account or sign-inexternally_connectable(matches) — web pages cannot message the extension; declared withids: []to explicitly block all other extensions too
The extension defaults to off in incognito windows — no content script activity, no API calls to OpenFoodFacts. If the user has enabled "Allow in Incognito" for the extension in Chrome settings, they can opt in for a specific session via the popup. Session state is stored in chrome.storage.session, which Chrome clears automatically when the incognito window closes.
This behaviour exceeds the Chrome guidance requirement (which mandates blocking only local storage writes in incognito). No barcodes are sent to OpenFoodFacts in private browsing without explicit user consent.
| Data | Destination | Trigger | Purpose |
|---|---|---|---|
| EAN-13 barcode | world.openfoodfacts.org/api/v2/product/{barcode}.json |
On each supported supermarket product page load | Look up cached NOVA score |
| Ingredient text (public label data) | world.openfoodfacts.org/api/v3/product/test |
When barcode lookup returns no NOVA score | Classify by ingredients |
Nothing else leaves your machine. In particular, the following are never transmitted:
- Your IP address is not exposed beyond what a normal HTTPS request reveals to the server (standard for all internet traffic)
- No browsing URLs, page content, or navigation patterns
- No cookies, account credentials, or session tokens
- No personal information of any kind
- All requests use HTTPS only — no HTTP fallbacks
- Fetch calls have explicit timeouts (8s barcode lookup, 10s ingredient analysis)
- All external API calls (OpenFoodFacts, and each retailer's own product API) are made exclusively from the background service worker — content scripts are fetch-free. This is enforced for all six supported adapters as of ADR-014/ADR-015.
| Key pattern | Contents | TTL |
|---|---|---|
off_{barcode} |
{ novaScore, productName, markers, offUrl, timestamp } |
7 days |
ingredients_{ingredientHash} |
Same structure — key is a djb2 hex hash of the ingredient text, so reformulated products automatically bust the cache | 7 days |
debugMode |
boolean |
Until cleared by user |
No PII is stored. The cache can be cleared at any time via the popup "Clear Cache" button.
Cache entries are encrypted at rest using AES-256-GCM (lib/storage-crypto.js) with a per-install random key. Raw classification data is never written to chrome.storage.local in plaintext.
Incognito tabs: Cache writes are skipped entirely when the request originates from an incognito tab, preventing product lookups from being persisted across private browsing sessions. Cache reads are still permitted (a cache hit reveals nothing about the current session).
| Key pattern | Contents | Lifetime |
|---|---|---|
nova_tab_{tabId} |
{ novaScore, productName, barcode, markers } — the NOVA result for the active product on that tab |
Until the tab is closed or navigates to a new URL |
This is used to display the current product's NOVA score in the popup without requiring a fresh lookup. It is explicitly cleared via chrome.tabs.onRemoved and chrome.tabs.onUpdated listeners, and is wiped automatically when the browser closes.
- No remote code execution: Chrome's MV3 policy blocks loading scripts from remote URLs. All JavaScript is bundled at install time and reviewed during Chrome Web Store publication.
- No persistent background page: The service worker is terminated when idle. No code runs continuously in the background.
- No
eval()ornew Function(): Zero dynamic code execution vectors anywhere in the codebase. - No CDN-loaded scripts: All dependencies are local.
- The content script runs in an isolated world — it cannot access the page's JavaScript variables or
localStorage. - DOM modifications are limited to injecting badge elements alongside product listings.
- All user-visible text is set via
textContent.innerHTMLis not used anywhere in the extension; badge DOM updates usereplaceChildren(), preventing XSS.
- The background service worker validates that messages originate from a supported supermarket tab (tesco.com, sainsburys.co.uk, asda.com, morrisons.com, waitrose.com, ocado.com) before processing them.
externally_connectableis declared withids: []and nomatches, explicitly blocking all other extensions and web pages from messaging the extension.- Message payloads are validated by
background/message-validator.jsbefore processing:novaScoremust be an integer in range 1–4barcodemust be a 12- or 13-digit numeric stringingredientsTextis capped at 50,000 characterstabIdmust be a positive integer;productNameis clamped to 200 characters
- Popup-only handlers (
GET_PAGE_NOVA,CLEAR_CACHE) are gated on!sender.tab, preventing content scripts from invoking them even from allowed origins. - ASDA auth tokens are rejected if they contain
\ror\ncharacters, preventing HTTP header injection.
| Attack Vector | Feasibility | Mitigation |
|---|---|---|
| Malicious OpenFoodFacts response | Low | Responses are parsed with validation; no eval() on response data |
| XSS via ingredient text | Low | Ingredient text is set via textContent only |
| Content script → background message injection | Very Low | Sender origin validated with exact equality against all 6 supported supermarkets; payloads validated (score, barcode, text length) by message-validator.js; popup-only handlers gated on !sender.tab; externally_connectable blocks all other extensions and web pages |
| Supply chain (npm) | Low | No runtime npm dependencies; all code is bespoke |
| Injection via dynamic API parameters | Very Low | All dynamic values (barcodes, product IDs, SKUs) are URL-encoded before use in fetch URLs across all adapters |
| Local storage read (e.g. by another extension) | Very Low | Cache entries encrypted with AES-256-GCM; ciphertext is useless without the per-install key |
Even if fully compromised, the extension cannot:
- Read your browsing history or tabs
- Access other websites' content
- Intercept or modify network requests
- Read or write cookies
- Access your clipboard
- Exfiltrate data to any destination other than
world.openfoodfacts.org
OpenFoodFacts is an open-source, nonprofit food data project. Their privacy policy is available at: https://world.openfoodfacts.org/privacy
Queries to their API may be logged server-side (as with any HTTP server). The only data sent is barcode numbers and ingredient text — both public information found on food packaging.
Please do not report security vulnerabilities through public GitHub issues.
Use GitHub's private security advisory feature:
- Go to the Security tab of this repository
- Click Report a vulnerability
- Fill in the details
We'll acknowledge your report within 48 hours and aim to release a fix within 14 days for critical issues.