Uma plataforma de aprendizado que permite aos alunos compor consultas MongoDB abandonou o módulo vm do Node.js e agora analisa cada consulta em uma árvore de sintaxe abstrata (AST) antes da execução. A mudança remove um sandbox que poderia ser quebrado, protegendo o backend de códigos arbitrários que uma string malformada poderia injetar.
Por que o sandbox original falhou
A primeira implementação envolvia um handle de banco de dados ativo em uma chamada vm.runInContext e tentava restringir os usuários com uma expressão regular. Ela permitia apenas nomes de métodos como find ou aggregate; todos os outros identificadores deveriam ser filtrados.
Duas falhas tornaram essa abordagem insegura:
- A filtragem por regex pode ser contornada. O JavaScript permite que o código use a notação de colchetes (
obj["constructor"]) para acessar qualquer propriedade. Um invasor pode recuperar o construtorFunction, construir uma nova função e executar qualquer código que desejar. A regex nunca vê o acesso à propriedade subjacente porque o código-fonte pode ser reescrito de inúmeras maneiras. vmnão é uma fronteira de segurança. A documentação do Node afirma que ovmisola o objeto global, mas não o processo como um todo. Ao injetar uma conexão de banco de dados ativa no sandbox, o código dentro do contexto retinha a capacidade de chamar qualquer método nessa conexão, incluindo aqueles que escrevem ou deletam dados. O sandbox não impedia o código de afetar o processo host.
A solução baseada em AST
A equipe substituiu a execução de código por análise estática. As strings de consulta agora alimentam o parser acorn, que produz uma AST — uma representação em árvore da estrutura sintática do código. A AST é examinada nó por nó contra uma lista de permissões (whitelist) rigorosa:
- Literais, arrays e objetos são permitidos apenas quando aparecem como valores simples.
- Chamadas de método são limitadas a um conjunto predefinido (
find,sort,limit, etc.). Qualquer outra chamada é rejeitada. - Acesso a propriedades computadas (ex:
obj[expr]) ou qualquer tipo de nó não listado explicitamente aciona uma falha imediata.
Como o parser trabalha na árvore, e não no texto bruto, ele não pode ser enganado por grafias alternativas ou truques de notação de colchetes. Uma cadeia de construtores que teria passado pela regex aparece como um nó não reconhecido e é rejeitada antes que qualquer código seja executado.
O que isso significa para a segurança
O novo design segue uma filosofia de "negar por padrão":
- Defina o que é permitido, não o que é proibido. Listas de permissão textuais não podem ser exaustivas; uma AST possui um conjunto finito de tipos de nós, tornando a verificação exaustiva viável.
- Nunca exponha recursos ativos dentro de um sandbox. Passar um handle de banco de dados para um contexto isolado dá ao código no sandbox uma linha direta para o backend. A abordagem de parser nunca entrega um objeto ativo ao código do usuário; ela apenas extrai a intenção da consulta.
- Valide a estrutura e, em seguida, execute com segurança. Assim que a AST passa pela validação, a plataforma traduz as chamadas permitidas em métodos reais do driver do MongoDB usando seu próprio caminho de código confiável.
O que observar a seguir
- Audite qualquer uso de
eval,new Functionouvmem sua base de código. Mesmo uma whitelist pode ser subvertida pela natureza dinâmica do JavaScript. - Adote o parsing de AST para código gerado pelo usuário sempre que possível. Bibliotecas como
acorn,esprimaoubabel-parsertornam a transformação direta. - Limite a exposição de objetos ativos. Se uma conexão de banco de dados, um handle de arquivo ou um socket de rede precisar estar acessível, envolva-o em um proxy que exponha apenas os métodos mínimos que você pretende permitir.
- Automatize os testes de casos de borda. Gere consultas que usem notação de colchetes, chaves computadas ou manipulação de protótipo para verificar se o seu parser as rejeita.
Conclusão
Confiar no vm.runInContext como um sandbox gera uma falsa sensação de segurança; filtros de regex não conseguem cobrir a sintaxe flexível do JavaScript, e o sandbox não isola os recursos do processo. Fazer o parsing da entrada do usuário em uma AST e incluir na whitelist apenas os nós que você compreende fornece uma barreira concreta e sustentável que interrompe o código malicioso antes de sua execução. Se a sua plataforma permite que os usuários escrevam código, substitua a execução do estilo eval por análise estática hoje mesmo.
