Why Does My Chrome Extension Say "Read and Change All Your Data on the Websites You Visit"?
5 min readModerok team
Which manifest keys trigger the scary Chrome "read and change all your data" permission warning, and three ways to narrow or remove it.
If your Chrome extension shows users "Read and change all your data on the websites you visit" at install, the cause is a broad host permission in your manifest, not something Chrome added on its own. That exact string is Chrome's warning for access to every site. It is triggered by a broad host match pattern (<all_urls>, https://*/*, http://*/*, or *://*/*) in your host_permissions key, or by an equally broad pattern in content_scripts.matches. The fix is to grant your extension less: narrow the match pattern to the sites you actually touch, switch to the activeTab permission, or make the host access optional and request it at runtime.
This post shows exactly which manifest fields produce the warning, how the wording scales with how much access you ask for, and the three concrete ways to shrink or remove it.
What triggers the warning
Chrome does not warn about your code; it warns about the access your manifest declares. Host access is the sensitive part, because an extension that can read and modify page content on every site can read passwords, cookies, and everything else you see in the browser. Chrome translates that capability into plain language for the install prompt.
Two manifest fields grant host access, and either one on its own is enough to produce the broad warning:
{
"manifest_version": 3,
"name": "My Extension",
"host_permissions": ["<all_urls>"],
"content_scripts": [
{ "matches": ["<all_urls>"], "js": ["content.js"] }
]
}
Both the host_permissions array and the matches array in content_scripts count toward what Chrome discloses. A common surprise: developers remove <all_urls> from host_permissions, keep it in a content script's matches, and the warning does not budge. If you want the warning gone, both have to be narrowed.
The warning text scales with the access you request
Chrome does not show a single fixed sentence. The wording is derived from the match patterns you declare, so a narrower request produces a narrower, less alarming message:
| What you declare | Warning the user sees |
|---|---|
<all_urls>, https://*/*, http://*/*, *://*/* | Read and change all your data on the websites you visit |
https://example.com/* (one host) | Read and change your data on example.com |
| A short list of specific hosts | Read and change your data on a named list of sites |
"tabs" permission | Read your browsing history |
"activeTab" | (no install warning) |
The jump from "all your data on the websites you visit" to "your data on example.com" is the difference between a prompt that makes cautious users bounce and one they read and accept. If your extension genuinely only works on one or a few sites, declaring those exact hosts is the single highest-leverage change you can make.
Fix 1: Narrow your match patterns
Most extensions ask for far more host access than they use. An extension that only enhances GitHub does not need <all_urls>; it needs GitHub:
{
"host_permissions": ["https://github.com/*"],
"content_scripts": [
{ "matches": ["https://github.com/*"], "js": ["content.js"] }
]
}
Match patterns support subdomains and paths, so you can be precise: https://*.example.com/* covers all subdomains, while https://app.example.com/* covers just one. List each host you actually operate on. The narrower the pattern, the narrower Chrome's warning, and the smaller your attack surface if the extension is ever compromised.
Fix 2: Use activeTab instead of broad host access
If your extension only needs to touch a page when the user explicitly invokes it (clicks your toolbar icon, picks a context menu item, or triggers a keyboard command), you often do not need standing host permissions at all. The activeTab permission grants temporary access to the current tab in response to a user gesture, and it shows no install-time warning:
{
"manifest_version": 3,
"name": "My Extension",
"permissions": ["activeTab", "scripting"],
"action": { "default_title": "Run on this page" }
}
// service worker: inject only after the user clicks the action
chrome.action.onClicked.addListener(async (tab) => {
await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["content.js"],
});
});
The gestures that grant activeTab are the action click, a context menu item, a keyboard command from the commands API, or accepting an omnibox suggestion. Access lasts while the user stays on that page and is revoked when they navigate away or close the tab. For "click the button to do a thing on this page" extensions, this replaces <all_urls> outright and takes the warning with it.
Fix 3: Make host access optional and request it at runtime
When you legitimately need broad access but not for every user or not at install, declare it as optional and ask only when the feature is used. Optional host permissions go in the optional_host_permissions key and produce no install-time warning:
{
"manifest_version": 3,
"name": "My Extension",
"optional_host_permissions": ["https://*/*"]
}
Then request the specific origins you need at runtime, from inside a user gesture (the request prompt will be blocked otherwise):
button.addEventListener("click", async () => {
const granted = await chrome.permissions.request({
origins: ["https://example.com/*"],
});
if (granted) startFeatureFor("https://example.com");
});
The user sees the request in context, tied to the feature they just asked for, instead of a blanket warning before they have tried anything. If you only discover the target hosts at runtime, the https://*/* wildcard in optional_host_permissions lets you request any HTTPS origin later. You can also check what you already hold with chrome.permissions.contains() and drop access you no longer need with chrome.permissions.remove().
Why it is worth the effort
The warning is not just cosmetic. It is the first thing a cautious user reads, and "read and change all your data on the websites you visit" is exactly the phrasing security-conscious people have learned to distrust. It also shapes your Chrome Web Store review: broad host access invites more scrutiny and a stricter justification under the platform's minimum-permissions expectations, and it expands what you have to declare in the Web Store privacy practices form. (For the disclosure side of this, see the Chrome Web Store data collection disclosure.) Requesting the least access that makes your extension work is the rare change that improves install conversion, review odds, and security at the same time.
One nuance worth knowing: not every capability needs host permissions. An extension can fetch() a remote API from its service worker without any host permission as long as that server returns the right CORS headers, so talking to your own backend does not require broadening the warning. That is how the Moderok analytics SDK works: it requires the "storage" permission, sends events keyed to a random profile id to the ingestion API over CORS, and never asks for host access. You still control the properties attached to custom events and must disclose what you send. If you want installs, active users, and event trends without adding a host permission to your manifest, start with the Moderok docs.