Los permisos innecesarios generan preguntas durante la revisión y avisos al instalar. Solicita solo el acceso que necesitan tus funciones y explica por qué.
Esta guía recorre los permisos de MV3, cómo reducir el conjunto inicial y cómo evitar errores al publicarlos.
Las categorías de permisos
Puedes organizar los permisos en tres grupos:
1. Capacidades declaradas en permissions
Se declaran en el manifiesto. Algunos generan avisos al instalar; otros no.
{
"permissions": [
"activeTab",
"storage",
"alarms",
"notifications"
]
}
Ejemplos habituales:
activeTab: acceso temporal a la pestaña después de una acción compatible del usuario, sin aviso de acceso permanente al instalar.storage: acceso achrome.storage, sin aviso de instalación.alarms: programación de alarmas, sin aviso de instalación.tabs: acceso a propiedades sensibles, como URL y título, de otras pestañas. Puede mostrar un aviso sobre el historial.notifications: notificaciones de escritorio, con el aviso correspondiente.scripting: necesario para inyectar scripts mediantechrome.scripting.executeScript, junto al acceso a la página.
2. Acceso a sitios mediante host_permissions
Determina los sitios a los que puede acceder la extensión:
{
"host_permissions": [
"https://api.example.com/*",
"https://*.google.com/*"
]
}
<all_urls> y https://*/* solicitan un acceso amplio y pueden producir avisos sobre leer y cambiar datos de sitios. Debes poder justificar ese alcance.
3. Acceso bajo demanda con optional_permissions
No se concede al instalar. Se solicita cuando hace falta mediante chrome.permissions.request():
{
"optional_permissions": ["bookmarks", "history"],
"optional_host_permissions": ["https://*.github.com/*"]
}
Así puedes pedir una capacidad al activar la función relacionada en lugar de exigir todo desde el principio.
Cómo aprovechar activeTab
Concede acceso temporal a la pestaña actual tras una interacción explícita, como pulsar el icono, usar un atajo compatible o elegir una acción del menú contextual.
Permite:
- Leer la URL y el título de esa pestaña.
- Inyectar con
chrome.scripting.executeScriptsi también declarasscripting. - Acceder al contenido permitido de esa página.
Sus límites:
- No concede acceso a otras pestañas.
- Necesita una interacción del usuario.
- El acceso termina al cerrar la pestaña o navegar a otro origen. Cambiar de pestaña por sí solo no lo revoca; consulta la documentación de activeTab.
Compara los avisos de instalación:
// BAD, shows "Read your browsing history" warning
{ "permissions": ["tabs"] }
// GOOD, no warning, sufficient for most use cases
{ "permissions": ["activeTab"] }
Muchos métodos de pestañas no requieren tabs; ese permiso se usa para acceder a sus propiedades sensibles. Para leer título y URL tras pulsar el icono, activeTab suele bastar.
Es el enfoque del tutorial inicial de TypeScript: un popup con acceso temporal a la pestaña.
Solicitar permisos opcionales durante el uso
// src/features/bookmarks.ts
async function enableBookmarkSync(): Promise<boolean> {
// This shows a focused permission prompt
const granted = await chrome.permissions.request({
permissions: ["bookmarks"],
});
if (!granted) {
console.log("User denied bookmark access");
return false;
}
// Now you can use chrome.bookmarks API
const bookmarks = await chrome.bookmarks.getTree();
return true;
}
Esto permite:
- Reducir los permisos exigidos al instalar.
- Explicar la necesidad junto a la función que los usa.
- Justificar con más claridad el alcance de la extensión.
No garantiza una revisión más rápida ni una tasa concreta de instalaciones.
Política de seguridad de contenido
La CSP predeterminada de las páginas de extensión en MV3 es estricta:
script-src 'self'; object-src 'self';
Entre sus consecuencias:
- Los scripts inline, como
<script>alert('hi')</script>, no se ejecutan con esa política. eval()ynew Function()no están permitidos en esas páginas.- Los scripts remotos, como los cargados desde una CDN, quedan bloqueados.
Puedes declarar la política admitida en el manifiesto sin habilitar código remoto arbitrario:
{
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self'"
}
}
'wasm-unsafe-eval' permite los usos de WebAssembly contemplados por esa política. Añádelo solo si lo necesitas.
Para consultar una API externa, usa fetch() con los permisos de host correspondientes. Descargar datos no equivale a descargar código para ejecutarlo; revisa además cualquier política de conexión que hayas definido.
Motivos de rechazo y cómo abordarlos
Acceso amplio a sitios sin justificación
Problema: declarar <all_urls> o https://*/* sin necesitarlo.
Solución: limita los dominios. Si la función central necesita acceso amplio, explica el motivo en la ficha y en la información de privacidad.
Capacidades que la extensión no utiliza
Problema: pedir historial, marcadores u otras capacidades sin una función visible que las necesite.
Solución: elimina lo que sobra y usa permisos opcionales para funciones secundarias.
Falta de información de privacidad
Problema: acceder a datos del usuario sin la política e información exigidas por la tienda.
Solución: describe el tratamiento real de datos y añade la URL de privacidad a la ficha. La guía de publicación cubre ese paso.
Ejecución de código descargado
Problema: cargar scripts externos o ejecutar código recuperado desde una API.
Solución: incluye el código en el paquete y usa las peticiones de red para datos. Revisa las dependencias que usen evaluación dinámica.
Código cuyo comportamiento está oculto
Problema: ofuscar el código para esconder su función. No es lo mismo que la minificación habitual.
Solución: retira la ofuscación. La minificación normal está permitida. Consulta la política de legibilidad del código.
Lista de revisión de permisos
Antes de enviar el manifiesto:
- ¿Basta
activeTabpara acceder a la pestaña tras una acción del usuario? - ¿Puedes limitar los dominios en vez de pedir acceso general?
- ¿Hay permisos sin uso que puedas quitar?
- ¿Puedes solicitar alguna capacidad solo al activar su función?
- ¿Necesitas de verdad
'wasm-unsafe-eval'? - ¿Has preparado la información de privacidad para el acceso que realizas?
Ejemplo histórico de permisos de ResistGate
ResistGate necesita detectar accesos a sitios bloqueados y mostrar el reto. Este ejemplo recoge el diseño descrito originalmente en la guía:
{
"permissions": ["storage", "alarms", "activeTab", "scripting"],
"host_permissions": ["<all_urls>"],
"optional_permissions": ["notifications"]
}
storageconserva la lista y los datos de sesión mediante almacenamiento tipado.alarmscontrola las ventanas de tiempo ganado.activeTabyscriptingcubren interacciones e inyección según el contexto y el acceso concedido.<all_urls>permite cubrir los dominios que el usuario añada a su lista.notificationses opcional para los recordatorios.
El acceso amplio debe responder a esa función central y estar explicado con claridad. La información de privacidad debe describir el comportamiento real de la versión que publicas.
Explicar el acceso a los usuarios
Los permisos también afectan a los mensajes y scripts de contenido. Si una función falla en un sitio, comprueba si tiene acceso a ese dominio y si la página permite la inyección.
Dos enfoques útiles:
- Solicitar acceso de forma gradual, junto a la función que lo necesita.
- Explicar los permisos al instalar, con una página que detalle para qué sirve cada uno.
Los próximos pasos
- Conecta una API con la guía de Claude para extensiones.
- Prepara la tienda con la lista de publicación.
- Revisa las decisiones del producto en cómo construí ResistGate.
Esta nota forma parte de la serie Extensiones de Chrome: de cero a producto, donde explico lo que aprendí construyendo ResistGate y Amethyst, desde el primer popup hasta la publicación.
Un pequeño voto de confianza
¿Valió la pena leer esta nota?