Usando o Proxy do ES2015 para se divertir e obter resultados
Alon Niv
23 de agosto de 2016
0 minutos de leituraMuito já foi escrito sobre o ES2015 — com suas funções de seta, declarações de variáveis com escopo e classes controversas. No entanto, um recurso específico recebeu pouca atenção até agora: o Proxy. Como desenvolvedores de JS, não estamos acostumados a depender de mecanismos de interceptação em toda a nossa base de código, mas eles têm várias aplicações muito úteis. Para citar algumas:
Testes, mocks e monkeypatching
Os padrões de projeto
ObservereVisitorAbstrações para conceitos complexos
Até agora, a linguagem não nos oferecia nenhum mecanismo desse tipo. Acho que o Proxy resolve esse problema sem deixar de lado o estilo de JS que conhecemos e adoramos (ou seja, sem métodos [Symbol.__setattr__] nos nossos objetos). Neste post, vou dar um exemplo das interceptações mais comuns (get, set, has e deleteProperty) e, no espírito da Snyk, o assunto será dependências.
Nosso objetivo
Queremos criar um pacote que permita a um desenvolvedor usar require com segurança, limitando o acesso a módulos específicos. Afinal, não queremos confiar o nosso fs a um módulo de utilitários funcionais, certo?
Preparando o terreno
O construtor Proxy aceita 2 parâmetros:
target: o objeto que queremos usar como proxyhandler: um objeto que contém a especificação das interceptações que queremos tratar (nesse contexto, uma interceptação é uma função chamada quando determinados eventos ocorrem no proxy, como o acesso, a atribuição ou a exclusão de uma propriedade) — veja os exemplos a seguir.
Como o require funciona
Só estou incluindo esta parte porque, embora a documentação do Node explique isso muito bem, vamos fazer algumas alterações engenhosas no cache dos módulos, então quero deixar claro o fluxo do require:
O módulo
xfazrequiredo móduloyO nome
yé resolvido para um caminho absoluto (ou não, caso seja um módulo nativo, comofs)O nome resolvido é buscado no cache, sem verificar se existe
Se o valor encontrado estiver definido (um objeto Module), os valores exportados são retornados
Caso contrário, o Node busca o arquivo no sistema de arquivos e o compila em um objeto Module, que é armazenado no cache; em seguida, seus valores exportados são retornados.
Esse cache é um singleton global, acessível por meio de require('module')._cache e require.cache, e é um objeto JS simples.
Agora, um pouco de código
Para simplificar, vamos supor que queremos apenas oferecer uma interface de lista de bloqueio para os módulos que não devem ser usados pelo módulo carregado com require. Ela será assim:
Beleza, agora já temos uma ideia de como vai ficar. Vamos começar a programar:
Legal, terminamos, certo? Bom, não exatamente. Sim, garantimos que nosso módulo mal-intencionado não consegue fazer require de um módulo bloqueado, mas ele ainda pode recorrer a outros truques:
Observação sobre require.cache: para corromper um único módulo no cache de módulos, use require.cache. Porém, se quiser substituir todo o mecanismo de cache, use require('module')._cache = .... Vamos ajustar nosso código para lidar com essa situação:
E... agora terminamos... certo? Não. Vamos considerar este código mal-intencionado:
Vamos dar só os últimos retoques:
E agora terminamos. Ou, pelo menos, acho que sim.
Você tem alguma ideia de como contornar esse proxy? Compartilhe com a gente no Twitter @snyksec!
O repositório desta POC está disponível aqui.
Comece a jogar Capture the Flag
Aprenda a resolver desafios de Capture the Flag assistindo à gravação sob demanda do nosso workshop virtual introdutório.


