Las páginas web normales pueden usar las APIs de fetch() o XMLHttpRequest para enviar y recibir datos de servidores remotos, pero están limitadas por la política de mismo origen. Las secuencias de comandos de contenido inician solicitudes
en nombre del origen web en el que se insertaron y, por lo tanto, las secuencias de comandos de contenido
también están sujetas a la política de mismo origen. Los orígenes de las extensiones no están tan limitados. Una secuencia de comandos
que se ejecuta en un service worker de extensión o en una pestaña en primer plano puede comunicarse con servidores remotos fuera de
su origen, siempre que la extensión solicite permisos de host.
Origen de la extensión
Cada extensión en ejecución existe dentro de su propio origen de seguridad independiente. Sin solicitar privilegios adicionales, la extensión puede llamar a fetch() para obtener recursos dentro de su instalación. Por ejemplo, si una extensión contiene un archivo de configuración JSON llamado config.json en una carpeta config_resources/, la extensión puede recuperar el contenido del archivo de la siguiente manera:
const response = await fetch('/config_resources/config.json');
const jsonData = await response.json();
Si la extensión intenta solicitar contenido de un origen de seguridad que no sea el suyo, por ejemplo, https://www.google.com, se tratará como una solicitud de origen cruzado, a menos que la extensión tenga permisos de host. Las solicitudes de origen cruzado siempre se tratan como tales en las secuencias de comandos de contenido, incluso si la extensión tiene permisos de host.
Solicita permisos de origen cruzado
Para solicitar acceso a servidores remotos fuera del origen de una extensión, agrega hosts, patrones de coincidencia, o ambos a la sección host_permissions del archivo de manifiesto.
{
"name": "My extension",
...
"host_permissions": [
"https://www.google.com/"
],
...
}
Los valores de permiso de origen cruzado pueden ser nombres de host completamente calificados, como los siguientes:
- "https://www.google.com/"
- "https://www.gmail.com/"
También pueden ser patrones de coincidencia, como los siguientes:
- "https://*.google.com/"
- "https://*/"
Un patrón de coincidencia de "https://*/" permite el acceso HTTPS a todos los dominios accesibles. Ten en cuenta que, aquí, los patrones de coincidencia son similares a los patrones de coincidencia de secuencias de comandos de contenido, pero se ignora cualquier información de ruta que siga al host.
También ten en cuenta que el acceso se otorga por host y por esquema. Si una extensión desea acceso HTTP seguro y no seguro a un host o conjunto de hosts determinado, debe declarar los permisos por separado:
"host_permissions": [
"http://www.google.com/",
"https://www.google.com/"
]
Fetch() vs. XMLHttpRequest()
fetch() se creó específicamente para los service workers y sigue una tendencia web más amplia que se aleja de las operaciones síncronas. La API de XMLHttpRequest() es compatible con las extensiones fuera del service worker, y llamarla activa el controlador de recuperación del service worker de la extensión. El trabajo nuevo debe favorecer a fetch() siempre que sea posible.
Consideraciones de seguridad
Evita las vulnerabilidades de secuencias de comandos entre sitios
Cuando uses recursos recuperados a través de fetch(), tu documento fuera de pantalla, panel lateral o ventana emergente debe tener cuidado de no
ser víctima de secuencias de comandos entre sitios. En particular, evita usar APIs peligrosas, como innerHTML. Por ejemplo:
const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// WARNING! Might be injecting a malicious script!
document.getElementById("resp").innerHTML = jsonData;
...
En cambio, prefiere APIs más seguras que no ejecuten secuencias de comandos:
const response = await fetch("https://api.example.com/data.json");
const jsonData = await response.json();
// JSON.parse does not evaluate the attacker's scripts.
let resp = JSON.parse(jsonData);
const response = await fetch("https://api.example.com/data.json");
const jsonData = response.json();
// textContent does not let the attacker inject HTML elements.
document.getElementById("resp").textContent = jsonData;
Limita el acceso de la secuencia de comandos de contenido a las solicitudes de origen cruzado
Cuando realices solicitudes de origen cruzado en nombre de una secuencia de comandos de contenido, ten cuidado de protegerte contra páginas web maliciosas que podrían intentar suplantar una secuencia de comandos de contenido. En particular, no permitas que las secuencias de comandos de contenido soliciten una URL arbitraria.
Considera un ejemplo en el que una extensión realiza una solicitud de origen cruzado para permitir que una secuencia de comandos de contenido descubra el precio de un artículo. Un enfoque no tan seguro sería que la secuencia de comandos de contenido especifique el recurso exacto que recuperará la página en segundo plano.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.contentScriptQuery == 'fetchUrl') {
// WARNING: SECURITY PROBLEM - a malicious web page may abuse
// the message handler to get access to arbitrary cross-origin
// resources.
fetch(request.url)
.then(response => response.text())
.then(text => sendResponse(text))
.catch(error => ...)
return true; // Will respond asynchronously.
}
}
);
chrome.runtime.sendMessage(
{
contentScriptQuery: 'fetchUrl',
url: `https://another-site.com/price-query?itemId=${encodeURIComponent(request.itemId)}`
},
response => parsePrice(response.text())
);
En el enfoque anterior, la secuencia de comandos de contenido puede pedirle a la extensión que recupere cualquier URL a la que tenga acceso. Una página web maliciosa puede falsificar esos mensajes y engañar a la extensión para que otorgue acceso a recursos de origen cruzado.
En cambio, diseña controladores de mensajes que limiten los recursos que se pueden recuperar. A continuación, la secuencia de comandos de contenido solo proporciona el itemId y no la URL completa.
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.contentScriptQuery == 'queryPrice') {
const url = `https://another-site.com/price-query?itemId=${encodeURIComponent(request.itemId)}`
fetch(url)
.then(response => response.text())
.then(text => parsePrice(text))
.then(price => sendResponse(price))
.catch(error => ...)
return true; // Will respond asynchronously.
}
}
);
chrome.runtime.sendMessage(
{contentScriptQuery: 'queryPrice', itemId: 12345},
price => ...
);
Prefiere HTTPS en lugar de HTTP
Además, ten especial cuidado con los recursos recuperados a través de HTTP. Si tu extensión se usa en una red hostil, un atacante de red (también conocido como "man-in-the-middle") podría modificar la respuesta y, potencialmente, atacar tu extensión. En cambio, prefiere HTTPS siempre que sea posible.
Ajusta la política de seguridad del contenido
Si modificas la Política de Seguridad del Contenido predeterminada para tu extensión agregando un
content_security_policy atributo a tu manifiesto, deberás asegurarte de que se permitan todos los hosts a los que
deseas conectarte. Si bien la política predeterminada no restringe las conexiones a los hosts, ten cuidado cuando agregues explícitamente las directivas connect-src o default-src.