Chrome Extension Security: The Manifest V3 Review Checklist (2026)

When the Chrome Web Store rejects your extension, the email does not say "too many permissions." It says Purple Potassium. Chrome names every violation with a colour and a chemical element, publishes what each one means, and expects you to look yours up. Ask an AI assistant to "build a Chrome extension that saves the page I'm on" and the manifest it writes will request <all_urls> and tabs, which is the exact shape that earns that code.

This checklist is ordered the way review is: the items that get you rejected or delisted first, then the ones that keep you from being exploited later. Each item names the manifest key it maps to and, where one exists, the rejection code it prevents.

TL;DR

21 Manifest V3 checks: request the narrowest permissions, bundle every line of JavaScript, isolate content-script data, check sender.id on every message, and set a strict CSP. The first five are what review bounces you for. The Web Store violation codes are published, so a rejection tells you exactly which item you skipped.

Manifest V2 is finished, not finishing. Chrome disabled V2 extensions for every user on every channel with Chrome 138 in July 2025, and Chrome 139 removed the ExtensionManifestV2Availability enterprise policy that had granted exemptions. Chrome's timeline puts 31 August 2026 as the date remaining V2 extensions come off the Web Store. If you inherited a V2 codebase, migration stopped being a roadmap item.

Critical: Fix Before Launch (5 Items)

Permission Management (4 Items)

Code Security (4 Items)

Data Handling (5 Items)

Privacy and Web Store Compliance (3 Items)

Manifest V3: What Actually Changed for Security

The Web Store stopped accepting new public V2 extensions in January 2022 and private ones that June, so V3 has been the only way in for years. What changed recently is the other end: enforcement finished. Three of the changes matter for security.

Service workers instead of background pages. Persistent background pages could hold long-running connections and accumulate state indefinitely. Service workers terminate after roughly 30 seconds of inactivity and restart on demand. This limits what a compromised background script can do between activations.

declarativeNetRequest instead of blocking webRequest. Under MV2, an extension with webRequest could intercept, read, and modify every HTTP request the browser made. Under MV3, request modification goes through rule-based declarativeNetRequest, which the browser evaluates without exposing raw request data to extension code. This removes a class of credential-harvesting extensions.

Remote code execution blocked. MV3 extensions cannot fetch JavaScript from a remote server and run it. All logic must be in the extension package that Chrome reviews. This closes the most common malware distribution vector: an extension that looks clean during review but downloads malicious code after install.

Chrome's deprecation timeline has the whole sequence: warning banners on pre-stable channels in June 2024, gradual disabling on stable from 9 October 2024, disabled by default everywhere on 31 March 2025, and no re-enabling at all from Chrome 138 in July 2025. Nothing about that is upcoming any more.

Read the Rejection Code, Not the Rejection Email

Most people resubmit after a rejection with a guess about what upset the reviewer. Chrome publishes the answer. Every rejection carries a code built from a colour and a chemical element, and Chrome's troubleshooting page lists what each one means. These are the ones a security-focused extension actually runs into:

CodeMeansUsual cause
Purple PotassiumExcessive permissions<all_urls> or a permission with no feature behind it
Blue ArgonManifest V3 requirementsRemotely hosted code, or eval() on a fetched string
Red TitaniumObfuscationBase64 or character-encoded source. Minification is fine
Purple LithiumMissing privacy policyCollecting user data with no accessible policy linked
Purple NickelNo prominent disclosureCollection starts before the user is told and consents
Purple CopperInsecure transmissionData sent over plain HTTP, or a token in a query string
Purple MagnesiumBrowsing activity collectionLogging pages visited without a user-facing feature needing it
Yellow ZincInsufficient metadataListing does not explain what the extension does
Red MagnesiumSingle purposeTwo unrelated features shipped in one extension

The four Purple codes are one family, and they are worth reading together: they are Chrome's user-data policy, split by which part you broke. Three of them (Lithium, Nickel, Magnesium) can be triggered by an analytics call you added without thinking about it.

Before you submit, read your own manifest.json next to your Web Store listing text and ask whether a stranger could match every permission to a sentence in the description. That pairing is roughly what review does, and it catches Purple Potassium and Yellow Zinc in the same pass.

Content Script Isolation in Practice

Content scripts run in an "isolated world": they share the page DOM but not the JavaScript globals. A page cannot read your extension's variables, and your extension cannot directly access a page's JavaScript objects.

What this does NOT protect you from:

  • DOM data is still untrusted. A malicious page can write arbitrary strings to DOM nodes your content script reads.
  • Message passing is not authenticated by default. Any site can send a message to your extension using window.postMessage or chrome.runtime.sendMessage if you listen without checking the sender.
Validate sender before acting on a message
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  // Only accept messages from our own extension
  if (sender.id !== chrome.runtime.id) {
    return false;
  }
  // Validate message structure
  if (typeof message.action !== 'string') {
    return false;
  }
  // Now safe to process
  handleMessage(message, sendResponse);
  return true; // Keep channel open for async response
});

Use TypeScript with strict types for your message payloads. Narrowing the union of valid message shapes at compile time catches a class of injection bugs before you ship them.

Why was my Chrome extension rejected?

The rejection email names a colour-and-element code, and Chrome publishes what each one means. The three that hit security-minded extensions hardest are Purple Potassium (excessive permissions, meaning you asked for more than your stated feature needs), Blue Argon (the Manifest V3 rule against remotely hosted code and eval of remote strings), and Red Titanium (obfuscation, where minification is fine but base64 or character encoding is not). Look up your code in Chrome's troubleshooting page before you resubmit, because a blind resubmit usually earns the same code twice.

What permissions should I avoid in Manifest V3?

Avoid broad host permissions like <all_urls> when specific patterns work. Avoid keeping webRequest in blocking mode (it is restricted in MV3). Use optional_permissions so users grant access only when they take a specific action inside the extension rather than at install time. Excessive permissions have their own rejection code, Purple Potassium, so this is a review problem and not only a security one.

Is Manifest V2 still supported in Chrome?

No. Chrome disabled Manifest V2 extensions for all users on all channels with Chrome 138 in July 2025, and the ExtensionManifestV2Availability enterprise policy that granted exemptions was removed in Chrome 139. Chrome's deprecation timeline lists 31 August 2026 as the date the remaining Manifest V2 extensions are removed from the Web Store entirely.

How do I pass Chrome Web Store security review faster?

Request the minimum permission set, explain each permission in the listing, publish a privacy policy if you touch user data, and keep the bundle unobfuscated. Those four map onto Purple Potassium, Yellow Zinc, Purple Lithium and Red Titanium, which are the codes most likely to bounce a security tool.

What is the content script isolated world in Manifest V3?

Content scripts run in an isolated JavaScript context that shares the DOM but not global variables with the host page. The page cannot read your extension's variables, but you must still sanitize data you read from the DOM before passing it to background scripts, since a malicious page can write arbitrary content to DOM nodes you read.

If your extension connects to a backend, scan it for vulnerabilities before your users install.

Security Checklists

Chrome Extension Security: The Manifest V3 Review Checklist (2026)