Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

WASI no Node.js: Guia Prático

Atualizado em: 12 de agosto de 2026

Rack de servidores processando fluxos de dados no Node.js

WebAssembly permite executar código compilado de linguagens como Rust, C e C++ em um formato portátil. Para que esse código acesse arquivos, argumentos, variáveis de ambiente e streams, ele precisa de uma interface com o sistema hospedeiro. O módulo WASI no Node.js implementa parte da WebAssembly System Interface e permite instanciar módulos WebAssembly que usam APIs semelhantes às de sistemas POSIX.

WASI pode ser útil para ferramentas portáteis, componentes escritos em outra linguagem e módulos que precisam de uma interface padronizada. Entretanto, a implementação atual do Node.js é experimental e não oferece uma sandbox completa para executar código não confiável. Preopens, argumentos e variáveis ajudam a limitar capacidades, mas não devem ser tratados como uma barreira de segurança absoluta.

Neste guia, você aprenderá a criar uma instância de WASI, carregar um arquivo .wasm, configurar argumentos, ambiente e diretórios, escolher entre start() e initialize(), capturar códigos de saída, trabalhar com streams e entender os limites de segurança.

O que é WASI?

WASI é um conjunto de especificações de APIs para aplicações compiladas para WebAssembly. O objetivo é oferecer interfaces padronizadas para recursos como arquivos, relógios, números aleatórios, streams e rede, dependendo da versão e do runtime.

O site oficial WASI.dev explica o desenvolvimento do padrão, suas versões e o modelo baseado em capacidades. No Node.js, o módulo node:wasi fornece uma implementação hospedeira. A documentação oficial de WASI no Node.js deve ser consultada para a versão instalada.

Status experimental

A documentação atual classifica node:wasi como experimental. Isso significa que APIs, requisitos e comportamentos podem mudar. Fixe uma versão do Node.js, cubra a integração com testes e acompanhe notas de versão antes de atualizar o runtime.

Não construa uma API pública estável diretamente sobre detalhes experimentais sem uma camada de abstração própria.

WASI não é uma sandbox segura no Node.js

O Node.js alerta que sua implementação não oferece as mesmas garantias de isolamento de alguns runtimes WASI dedicados. O modelo de capacidades é suportado, mas o runtime não deve ser usado para executar módulos hostis como se estivessem completamente isolados.

Para código não confiável, considere:

  • um runtime WASI com modelo de segurança apropriado;
  • processo separado com usuário restrito;
  • container com filesystem e rede limitados;
  • limites de CPU, memória e tempo;
  • validação da origem e assinatura do módulo;
  • monitoramento e destruição do ambiente após a execução.

O guia de Módulo VM no Node.js apresenta uma advertência semelhante: separar contexto JavaScript não equivale a criar isolamento de segurança completo.

Importando o módulo

Em ES Modules:

import { WASI } from 'node:wasi';

Em CommonJS:

const { WASI } = require('node:wasi');

A opção version é obrigatória nas versões modernas do Node.js. Para módulos Preview 1, utilize:

const wasi = new WASI({
  version: 'preview1'
});

Carregando um módulo WebAssembly

import { readFile } from 'node:fs/promises';
import { WASI } from 'node:wasi';

const wasi = new WASI({
  version: 'preview1'
});

const bytes = await readFile(
  new URL('./application.wasm', import.meta.url)
);

const module = await WebAssembly.compile(bytes);
const instance = await WebAssembly.instantiate(
  module,
  wasi.getImportObject()
);

const exitCode = wasi.start(instance);
console.log({ exitCode });

getImportObject() cria o objeto de imports esperado pela versão escolhida. start() executa o export _start de um programa WASI no formato command.

O artigo de File System no Node.js explica leitura assíncrona e tratamento de arquivos.

Compilar ou instanciar diretamente

Também é possível usar WebAssembly.instantiate() diretamente com bytes:

const { instance } = await WebAssembly.instantiate(
  bytes,
  wasi.getImportObject()
);

wasi.start(instance);

Separar compilação e instanciação é útil quando o mesmo módulo compilado será instanciado mais de uma vez. Cada instância deve receber seu próprio ambiente e ciclo de execução adequado.

Argumentos

const wasi = new WASI({
  version: 'preview1',
  args: [
    'tool.wasm',
    '--input',
    '/data/input.txt'
  ]
});

O primeiro item normalmente representa o caminho virtual do comando. Não repasse argumentos do processo hospedeiro automaticamente sem filtrar:

const allowedArgs = process.argv
  .slice(2)
  .filter(value => value.length < 500);

Valide quantidade, tamanho e formato para evitar consumo excessivo ou comportamento inesperado.

Variáveis de ambiente

const wasi = new WASI({
  version: 'preview1',
  env: {
    MODE: 'production',
    LANGUAGE: 'pt-BR'
  }
});

Nunca passe process.env inteiro. Ele pode conter tokens, credenciais de banco, chaves de API e configurações internas. Crie uma allowlist explícita:

const env = {
  MODE: process.env.MODE || 'production',
  LOG_LEVEL: process.env.LOG_LEVEL || 'info'
};

Veja Variáveis de Ambiente no Node.js para validação e proteção de segredos.

Preopens

Preopens mapeiam caminhos vistos pelo módulo para diretórios reais do host:

const wasi = new WASI({
  version: 'preview1',
  preopens: {
    '/input': '/srv/application/input',
    '/output': '/srv/application/output'
  }
});

O módulo enxerga caminhos virtuais como /input, mas o Node.js acessa os diretórios correspondentes no host.

Escolhendo diretórios seguros

Use diretórios dedicados, permissões mínimas e conteúdo temporário. Não exponha:

  • a raiz do sistema;
  • o diretório do projeto inteiro;
  • pastas de credenciais;
  • sockets administrativos;
  • diretórios de outros usuários;
  • volumes com dados desnecessários.

Normalize e resolva caminhos configurados pelo administrador, não pelo usuário final. O guia de Módulo Path no Node.js explica contenção de diretórios e path traversal.

Permissões do sistema operacional

Mesmo com preopens, aplique permissões no filesystem. Execute o processo com usuário sem privilégios e monte volumes somente leitura quando escrita não for necessária.

Defesa em profundidade é essencial porque a implementação atual do Node.js não promete sandbox completa.

stdin, stdout e stderr

Por padrão, WASI usa os file descriptors 0, 1 e 2:

const wasi = new WASI({
  version: 'preview1',
  stdin: 0,
  stdout: 1,
  stderr: 2
});

Isso conecta o módulo aos streams do processo hospedeiro. Em serviços, considere redirecionar a saída para arquivos ou pipes controlados.

Capturando saída em arquivo

import { open } from 'node:fs/promises';

const output = await open('./wasi-output.log', 'w');
const errors = await open('./wasi-error.log', 'w');

try {
  const wasi = new WASI({
    version: 'preview1',
    stdout: output.fd,
    stderr: errors.fd
  });

  // Compile, instancie e execute.
} finally {
  await output.close();
  await errors.close();
}

Use limites, rotação e limpeza. Um módulo pode produzir saída ilimitada e preencher o disco.

returnOnExit

Por padrão, versões modernas retornam o código solicitado pelo módulo em vez de encerrar todo o processo:

const wasi = new WASI({
  version: 'preview1',
  returnOnExit: true
});

const exitCode = wasi.start(instance);

Esse comportamento é importante quando o Node.js hospeda vários trabalhos. Definir returnOnExit: false permite que uma chamada de saída do módulo encerre o processo Node.js, o que raramente é desejável em servidores.

Command e reactor

Um módulo no formato command exporta _start e é executado com wasi.start(instance). Um reactor pode exportar _initialize e ser preparado com:

wasi.initialize(instance);

Não misture os dois modelos. start() espera um command; initialize() espera um reactor. A documentação define as validações e erros quando os exports não correspondem.

finalizeBindings

Versões recentes do Node.js podem oferecer finalizeBindings(), que configura bindings sem chamar start() ou initialize(). Esse método é útil em cenários avançados, incluindo instâncias usadas em threads filhas e memória fornecida explicitamente.

Como a API é experimental e recente, verifique a disponibilidade na versão mínima suportada e mantenha um fallback.

Memória WebAssembly

start() e initialize() exigem uma memória WebAssembly apropriada, normalmente exportada como memory. Valide o módulo antes de executá-lo:

if (!(instance.exports.memory instanceof WebAssembly.Memory)) {
  throw new Error('O módulo não exporta memória WASM');
}

Limites de páginas devem ser considerados no momento da compilação do módulo. Crescimento de memória pode afetar o processo hospedeiro.

Limites de CPU e tempo

WASI não impede um módulo de executar um loop de CPU indefinido. Uma Promise com timeout não interrompe código WebAssembly síncrono no mesmo event loop.

Para impor um prazo real, execute em:

  • Worker Thread que pode ser terminada;
  • processo filho que pode receber signal;
  • container com limite de CPU;
  • runtime dedicado com fuel ou epoch interruption.

O conteúdo sobre Worker Threads no Node.js apresenta isolamento de trabalho de CPU, enquanto Child Process no Node.js mostra processos separados.

Executando em Worker Thread

Uma arquitetura segura para disponibilidade é carregar e executar o módulo em um worker:

const worker = new Worker(
  new URL('./wasi-worker.js', import.meta.url),
  {
    workerData: {
      modulePath,
      args,
      directories
    }
  }
);

const timeout = setTimeout(() => {
  worker.terminate();
}, 10000);

O encerramento do worker reduz o impacto sobre a thread principal, mas não transforma um módulo não confiável em seguro. Arquivos, rede e credenciais precisam continuar restritos.

Validação do módulo

const bytes = await readFile(modulePath);

if (!WebAssembly.validate(bytes)) {
  throw new Error('Módulo WebAssembly inválido');
}

Validação estrutural não confirma confiança. Verifique hash, assinatura, origem, tamanho e política de publicação.

Cache de módulos compilados

Compilar o mesmo binário repetidamente consome CPU. É possível manter cache por hash:

const moduleCache = new Map();

async function getCompiledModule(bytes) {
  const hash = createHash('sha256')
    .update(bytes)
    .digest('hex');

  if (!moduleCache.has(hash)) {
    moduleCache.set(hash, await WebAssembly.compile(bytes));
  }

  return moduleCache.get(hash);
}

Defina limite e expiração para impedir crescimento infinito.

Uma instância por execução

Estado linear, globais e recursos pertencem à instância. Para jobs independentes, crie uma nova instância com ambiente próprio. Não reutilize estado entre clientes sem que o módulo tenha sido projetado para isso.

Observabilidade

Registre:

  • hash do módulo;
  • versão da aplicação;
  • duração;
  • código de saída;
  • bytes lidos e escritos;
  • timeout;
  • diretórios virtuais concedidos;
  • uso de memória e CPU.

Não registre conteúdo sensível dos arquivos nem todas as variáveis de ambiente.

Tratamento de erros

try {
  const exitCode = wasi.start(instance);

  if (exitCode !== 0) {
    throw new Error(`WASI terminou com código ${exitCode}`);
  }
} catch (error) {
  logger.error('wasi_execution_failed', {
    name: error.name,
    message: error.message,
    moduleHash
  });
}

Diferencie erro de compilação, instanciação, import ausente, filesystem, timeout e código de saída do módulo.

Testes

Crie módulos pequenos para testar:

  • stdout;
  • argumentos;
  • variáveis permitidas;
  • leitura de preopen;
  • escrita bloqueada;
  • código de saída;
  • módulo sem memória;
  • módulo command e reactor;
  • limite de duração.

Execute testes nas mesmas versões do Node.js usadas em produção.

WASI e portabilidade

Um módulo pode funcionar em vários runtimes, mas suporte às versões e interfaces varia. O Node.js documenta atualmente suporte específico de versões no construtor. Não assuma que um componente WASI recente será compatível com a implementação Preview 1.

WASI Component Model

O ecossistema WASI evolui em direção ao Component Model e versões mais novas, com interfaces tipadas e composição entre linguagens. O módulo nativo do Node.js pode não oferecer imediatamente todos esses recursos. Avalie runtimes e ferramentas do ecossistema quando o projeto exige componentes modernos.

Erros comuns

  • Executar código hostil: a implementação do Node.js não é sandbox completa.
  • Passar process.env: credenciais ficam disponíveis ao módulo.
  • Preabrir diretórios amplos: arquivos desnecessários são expostos.
  • Executar na thread principal: um loop bloqueia todo o serviço.
  • Usar returnOnExit false: o módulo pode encerrar o servidor.
  • Ignorar versão experimental: atualização quebra a integração.
  • Não limitar saída: disco ou memória podem ser consumidos.

Boas práticas para produção

  • Fixe e teste a versão do Node.js.
  • Não trate node:wasi como sandbox segura.
  • Use allowlist para argumentos e ambiente.
  • Conceda diretórios mínimos.
  • Execute com usuário sem privilégios.
  • Use worker, processo ou container para limites.
  • Valide hash, origem e tamanho do módulo.
  • Limite tempo, CPU, memória e saída.
  • Proteja logs e arquivos temporários.
  • Monitore mudanças na especificação WASI.

Conclusão

O WASI no Node.js permite hospedar aplicações WebAssembly que precisam de argumentos, ambiente, streams e acesso controlado a diretórios. A API é útil para integrar componentes compilados de outras linguagens e experimentar software portátil.

O benefício vem acompanhado de limites importantes: a API é experimental, o suporte de versões é específico e o Node.js não oferece isolamento completo para código não confiável. Com capacidades mínimas, processos restritos, limites de recursos e módulos verificados, WASI pode ser usado de forma consciente sem transformar portabilidade em um risco para o hospedeiro.

Os 10 Melhores Cursos de Programação de 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita