Como usar eval em extensões do Chrome

O sistema de extensões do Chrome aplica uma Política de Segurança de Conteúdo (CSP) padrão bastante rigorosa. As restrições da política são simples: o script precisa ser movido off-line para arquivos JavaScript separados, os manipuladores de eventos inline precisam ser convertidos para usar addEventListener e eval() é desativado. Os apps do Chrome têm uma política ainda mais rígida, e estamos muito satisfeitos com as propriedades de segurança que essas políticas oferecem.

No entanto, reconhecemos que várias bibliotecas usam construções semelhantes a eval() e eval, como new Function(), para otimizar o desempenho e facilitar a expressão. As bibliotecas de modelos são especialmente propensas a esse estilo de implementação. Embora alguns (como o Angular.js) ofereçam suporte a CSP de forma nativa, muitos frameworks conhecidos ainda não foram atualizados para um mecanismo compatível com o mundo sem eval das extensões. A remoção do suporte a essa funcionalidade provou ser mais problemática do que o esperado para os desenvolvedores.

Este documento apresenta o sandbox como um mecanismo seguro para incluir essas bibliotecas nos seus projetos sem comprometer a segurança. Para encurtar, vamos usar o termo extensões, mas o conceito se aplica igualmente aos aplicativos.

Por que usar sandbox?

eval é perigoso dentro de uma extensão porque o código que ela executa tem acesso a tudo na do ambiente de alta permissão da extensão. Há uma série de APIs chrome.* poderosas disponíveis que podem afetar gravemente a segurança e a privacidade de um usuário. A extração simples de dados é a menor das nossas preocupações. A solução oferecida é um sandbox em que eval pode executar código sem acesso aos dados da extensão ou às APIs de alto valor da extensão. Sem dados, sem APIs, sem problemas.

Fazemos isso listando arquivos HTML específicos dentro do pacote de extensão como sendo colocados no sandbox. Sempre que uma página em sandbox é carregada, ela é movida para uma origem exclusiva e será negada. acesso às APIs chrome.*. Se carregarmos essa página isolada em nossa extensão usando um iframe, podemos transmitir mensagens para ela, permitir que ela aja de alguma forma com base nessas mensagens e esperar que ela nos transmita um resultado. Esse mecanismo simples de mensagens nos dá tudo o que precisamos para incluir com segurança as mensagens geradas com eval no fluxo de trabalho da nossa extensão.

Criar e usar um sandbox.

Se você quiser mergulhar direto no código, pegue a extensão de exemplo de sandbox e comece. É um exemplo funcional de uma API de mensagens pequena criada com base na biblioteca de modelos Handlebars. Ela vai fornecer tudo o que você precisa para começar. Para quem quiser mais explicações, vamos analisar esse exemplo aqui.

Listar arquivos no manifesto

Cada arquivo que deve ser executado em uma sandbox deve estar listado no manifesto da extensão, adicionando um sandbox. Essa é uma etapa fundamental e fácil de esquecer. Verifique se o arquivo em sandbox está listado no manifesto. Neste exemplo, colocamos o arquivo no sandbox de forma inteligente. chamado "sandbox.html". A entrada do manifesto é assim:

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

Carregar o arquivo no sandbox

Para fazer algo interessante com o arquivo no modo sandbox, precisamos carregá-lo em um contexto em que ela pode ser resolvida pelo código da extensão. Aqui, sandbox.html foi carregado na Página de eventos (eventpage.html) da extensão usando um iframe. eventpage.js contém o código que envia uma mensagem para o sandbox sempre que a ação do navegador é clicada, encontrando o iframe na página e executando o método postMessage no contentWindow. A mensagem é um objeto contendo duas propriedades: context e command. Vamos falar sobre ambos em breve.

chrome.browserAction.onClicked.addListener(function() {
 var iframe = document.getElementById('theFrame');
 var message = {
   command: 'render',
   context: {thing: 'world'}
 };
 iframe.contentWindow.postMessage(message, '*');
});
Para informações gerais sobre a API postMessage, consulte a documentação da postMessage no MDN. Ela é bem completa e vale a pena ler. Especificamente, observe que os dados só podem ser transmitidos de um lado para outro se forem serializáveis. As funções, por exemplo, não são.

Fazer algo perigoso

Quando o sandbox.html é carregado, ele carrega a biblioteca Handlebars e cria e compila um objeto inline da mesma forma que a Handlebars sugere:

<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>

Isso não falha. Embora o Handlebars.compile acabe usando new Function, tudo funciona exatamente como esperado, e teremos um modelo compilado em templates['hello'].

Transmita o resultado de volta

Vamos disponibilizar esse modelo para uso configurando um listener de mensagens que aceita comandos da página de eventos. Vamos usar o command transmitido para determinar o que precisa ser feito. Você pode imaginar fazer mais do que apenas renderizar, talvez criar modelos? Talvez gerenciá-las de alguma forma o código?), e o context será transmitido diretamente ao modelo para renderização. O HTML renderizado será retornado à página do evento para que a extensão possa fazer algo útil com ela mais tarde:

<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>

Na página do evento, vamos receber essa mensagem e fazer algo interessante com os dados html que recebemos. Neste caso, vamos usar uma Notificação na área de trabalho, mas é totalmente possível usar esse HTML com segurança como parte da interface de usuário da extensão. Inserindo-a via O innerHTML não representa um risco de segurança significativo, já que mesmo um comprometimento total do ambiente em sandbox por algum ataque inteligente não poderiam injetar script perigoso ou conteúdo de plug-in o contexto da extensão com alta permissão.

Esse mecanismo simplifica a criação de modelos, mas ele não se limita a modelos. Qualquer um código que não funciona imediatamente sob uma Política de Segurança de Conteúdo rigorosa pode ser colocado no sandbox; no na verdade, pode ser útil colocar no sandbox componentes das suas extensões que seriam executados corretamente restringir cada parte do programa ao menor conjunto de privilégios necessários para sejam executados corretamente. A apresentação Como escrever apps da Web seguros e extensões do Chrome (em inglês) do Google O I/O 2012 dá alguns bons exemplos dessa técnica em ação e vale 56 minutos de sua tempo de resposta.