Cómo usar eval en las extensiones de Chrome

El sistema de extensiones de Chrome aplica una Política de Seguridad del Contenido (CSP) predeterminada bastante estricta. Las restricciones de la política son sencillas: la secuencia de comandos debe moverse fuera de línea a archivos JavaScript separados, los controladores de eventos intercalados deben convertirse para usar addEventListener y eval() está inhabilitado. Las apps para Chrome tienen una política aún más estricta, y estamos muy contentos con las propiedades de seguridad que proporcionan estas políticas.

Sin embargo, reconocemos que una variedad de bibliotecas usan construcciones similares a eval() y eval, como new Function(), para la optimización del rendimiento y la facilidad de expresión. Las bibliotecas de plantillas son este estilo de implementación. Algunos (como Angular.js) admiten CSP de inmediato, muchos frameworks populares aún no se actualizan a un mecanismo que sea compatible extensiones' Un mundo sin eval Por lo tanto, quitar la compatibilidad con esa funcionalidad resultó más problemático de lo esperado para los desarrolladores.

En este documento, se presenta el entorno de pruebas como un mecanismo seguro para incluir estas bibliotecas en tus proyectos sin comprometer la seguridad. Para abreviar, usaremos el término extensiones a lo largo del texto, pero el concepto se aplica de la misma manera a las aplicaciones.

¿Por qué usar una zona de pruebas?

eval es peligroso dentro de una extensión porque el código que ejecuta tiene acceso a todo en el entorno de permisos altos de la extensión. Hay una gran cantidad de APIs de chrome.* potentes disponibles que podrían tener un impacto grave en la seguridad y la privacidad del usuario el simple robo de datos es lo más importante. La solución que se ofrece es una zona de pruebas en la que eval puede ejecutar código sin acceso a los datos ni a las APIs de alto valor de la extensión. Sin datos, API ni problema.

Para ello, enumeramos archivos HTML específicos dentro del paquete de extensión como zona de pruebas. Cuando se cargue una página de zona de pruebas, se moverá a un origen único y se denegará acceso a las APIs de chrome.*. Si cargamos esta página en la zona de pruebas de nuestra extensión a través de un iframe, podemos pasarle mensajes, permitir que actúe sobre ellos de alguna manera y esperar a que nos devuelva un resultado. Este mecanismo de mensajería simple nos brinda todo lo que necesitamos para incluir de forma segura el código impulsado por eval en el flujo de trabajo de nuestra extensión.

Crear y usar una zona de pruebas

Si quieres profundizar directamente en el código, toma la extensión de ejemplo de la zona de pruebas y realiza desactivadas. Es un ejemplo funcional de una pequeña API de mensajería compilada en Handlebars. de plantillas, y debería darte todo lo que necesitas para comenzar. Para quienes quieran un poco más de explicación, analicemos ese ejemplo juntos.

Muestra una lista de archivos en el manifiesto

Cada archivo que deba ejecutarse dentro de una zona de pruebas debe incluirse en el manifiesto de la extensión agregando un sandbox. Este es un paso fundamental y es fácil de olvidar, así que vuelve a verificarlo. tu archivo de zona de pruebas aparece en el manifiesto. En este ejemplo, estamos probando el archivo en una zona de pruebas inteligente. llamada "sandbox.html". La entrada de manifiesto se ve de la siguiente manera:

{
  ...,
  "sandbox": {
     "pages": ["sandbox.html"]
  },
  ...
}

Carga el archivo de zona de pruebas

Para hacer algo interesante con el archivo en zona de pruebas, debemos cargarlo en un contexto en el que el código de la extensión pueda abordarlo. Aquí, sandbox.html se cargó en la página del evento (eventpage.html) de la extensión a través de un iframe. eventpage.js contiene código que envía un mensaje a la zona de pruebas cada vez que se hace clic en la acción del navegador buscando iframe en la página y ejecutando el método postMessage en su contentWindow. El mensaje es un objeto que contiene dos propiedades: context y command. Analizaremos ambos en un momento.

chrome.browserAction.onClicked.addListener(function() {
 var iframe = document.getElementById('theFrame');
 var message = {
   command: 'render',
   context: {thing: 'world'}
 };
 iframe.contentWindow.postMessage(message, '*');
});
Para obtener información general sobre la API de postMessage, consulta la documentación de postMessage sobre MDN . Es bastante completo y vale la pena leerlo. En particular, ten en cuenta que los datos solo se pueden pasar de un lado a otro si se pueden serializar. Por ejemplo, las funciones no lo son.

Haz algo peligroso

Cuando se carga sandbox.html, se carga la biblioteca de Handlebars, y se crea y compila una canalización en la forma en que los Handlebars sugieren lo siguiente:

<script src="handlebars-1.0.0.beta.6.js"></script>
<script id="hello-world-template" type="text/x-handlebars-template">
  <div class="entry">
    <h1>Hello, !</h1>
  </div>
</script>
<script>
  var templates = [];
  var source = document.getElementById('hello-world-template').innerHTML;
  templates['hello'] = Handlebars.compile(source);
</script>

Esto no falla. Si bien Handlebars.compile termina usando new Function, las cosas funcionan exactamente como se esperaba, y obtendremos una plantilla compilada en templates['hello'].

Pasa el resultado

Esta plantilla estará disponible para su uso configurando un objeto de escucha de mensajes que acepte comandos. en la página del evento. Usaremos el command que se pasa para determinar qué se debe hacer (podrías imaginar que haces más que renderizar, ¿tal vez crear plantillas? Quizás los administres en algún ?), y el context se pasará a la plantilla directamente para su renderización. El código HTML renderizado se volverá a pasar a la página del evento para que la extensión pueda hacer algo útil con él más adelante:

<script>
  window.addEventListener('message', function(event) {
    var command = event.data.command;
    var name = event.data.name || 'hello';
    switch(command) {
      case 'render':
        event.source.postMessage({
          name: name,
          html: templates[name](event.data.context)
        }, event.origin);
        break;

      // case 'somethingElse':
      //   ...
    }
  });
</script>

En la página del evento, recibiremos este mensaje y haremos algo interesante con los datos de html que se nos pasaron. En este caso, solo lo repetiremos a través de una notificación para computadoras, pero es posible usar este código HTML de forma segura como parte de la IU de la extensión. Insertarlo a través de innerHTML no representa un riesgo de seguridad significativo, ya que incluso una vulneración completa del código en zona de pruebas a través de un ataque inteligente no podría insertar contenido peligroso de secuencias de comandos o complementos en el contexto de la extensión de permisos altos.

Este mecanismo permite crear plantillas de forma sencilla, pero, por supuesto, no se limita a ese tipo de plantillas. Cualquier código que no funcione de forma predeterminada con una política de seguridad del contenido estricta se puede colocar en la zona de pruebas. De hecho, a menudo es útil colocar en la zona de pruebas los componentes de tus extensiones que se ejecutarían correctamente para restringir cada parte de tu programa al conjunto más pequeño de privilegios necesarios para que se ejecute correctamente. La presentación sobre escritura de aplicaciones web seguras y extensiones de Chrome de Google I/O 2012 ofrece algunos buenos ejemplos de estas técnicas en acción. Vale 56 minutos tiempo.