OAuth tokens and session tokens stored in localStorage are readable by any JavaScript running on the page — including third-party scripts, ad networks, and XSS payloads. Because extensions share the DOM with page scripts when using content scripts, a single XSS vulnerability on any site the user visits can silently extract a token and enable full account takeover. OWASP 2021 A02 and CWE-312 both call this out: storing sensitive credentials without isolation is a broken authentication failure. GDPR Art. 32 requires appropriate technical measures to protect personal data — storing auth tokens in localStorage fails that standard.
Critical because auth tokens in `localStorage` are accessible to any page script, making a single XSS injection sufficient to exfiltrate the token and impersonate the authenticated user across all their sessions.
Move all user authentication tokens from localStorage or sessionStorage into chrome.storage.local, which is sandboxed from page scripts:
// BAD — accessible to any page script
localStorage.setItem('token', response.access_token);
// GOOD — isolated to extension context
chrome.storage.local.set({ authToken: response.access_token });
// BEST — memory only (token lost on extension unload, no persistent attack surface)
let authToken = null;
authToken = response.access_token;
For tokens that must persist across sessions, use chrome.storage.local with encryption via a library like TweetNaCl.js rather than storing plaintext.
ID: extension-data-privacy.data-collection.tokens-not-in-storage
Severity: critical
What to look for: First, determine whether the extension has a user authentication flow (login, OAuth, session management). If there is no user auth flow, mark skip. If there is an auth flow, search content scripts and popup/options code for patterns that store user auth tokens in localStorage or sessionStorage. Look for localStorage.setItem('token', ...), sessionStorage.token = ..., or similar. Note: service API keys stored in chrome.storage.sync or chrome.storage.local are a separate concern handled by the no-sync-secrets check — do not evaluate service credentials here.
Skip (N/A) when: The extension does not authenticate users and has no user login/logout flow. Even if the extension stores service API keys or other credentials, mark skip if there are no user-owned authentication tokens to evaluate. Evaluate skip BEFORE pass/fail.
Pass criteria: Count all token storage locations in the codebase. Extension has a user auth flow AND user authentication tokens (OAuth tokens, session tokens, bearer tokens) are NOT stored in localStorage or sessionStorage. At least 100% of persistent tokens use chrome.storage.local or are stored in memory only. Report the count of storage locations found even on pass.
Fail criteria: User authentication tokens found in localStorage or sessionStorage. These are accessible to any script with DOM access, including injected scripts or XSS exploits.
Detail on fail: Identify the token types and storage location. Example: "OAuth access token stored in localStorage under key 'auth_token' — accessible to XSS attacks" or "API key hardcoded in localStorage and logged in background script."
Remediation: Move tokens from localStorage to chrome.storage.local or memory:
// BAD: localStorage
localStorage.setItem('token', response.access_token);
// GOOD: chrome.storage.local
chrome.storage.local.set({ authToken: response.access_token });
// BETTER: memory-only (lost on unload)
let authToken = null;