12k
All articles

O Modelo de Permissões do Deno Explicado

Permissões do Deno explicadas: flags allow e deny, escopo, conjuntos em deno.json, API Permissions e riscos de segurança.

OpenReplay Team
OpenReplay Team
O Modelo de Permissões do Deno Explicado

O Deno executa o seu código em um sandbox que não concede nada de antemão: sistema de arquivos, rede, variáveis de ambiente, subprocessos, informações do sistema e bibliotecas nativas (FFI) estão todos fechados até que você habilite cada um deles com uma flag --allow-*, e quase todas as flags aceitam um argumento que restringe a concessão a caminhos, hosts ou variáveis específicos.

Se você está chegando do Node, o seu primeiro script em Deno quase certamente vai travar em um erro de permissão, e a solução raramente é o -A generalizado que a memória muscular busca. Descobrir qual flag adicionar, e com que rigor delimitá-la, representa a maior parte da curva de aprendizado.

Isso é o inverso do comportamento padrão histórico do Node.js, e é a coisa mais importante a entender antes de executar qualquer script Deno. Este artigo detalha o que é bloqueado por padrão, cada flag --allow-* e --deny-* com a sintaxe de delimitação, as adições do Deno 2.x (precedência do deny, --allow-sys, --allow-import, wildcard em env), conjuntos de permissões no deno.json, a API de Permissões em tempo de execução e as duas armadilhas de segurança sobre as quais a documentação é mais silenciosa.

Pontos Principais

  • Por padrão, código Deno não pode ler ou gravar arquivos, abrir conexões de rede, ler variáveis de ambiente, criar subprocessos, acessar informações do sistema ou carregar bibliotecas nativas. Você opta por cada capacidade individualmente com uma flag --allow-*.
  • Toda flag --allow-* tem uma contraparte --deny-*, e o deny sempre vence: --allow-read=. --deny-read=./secrets concede acesso ao diretório do projeto, mas mantém ./secrets ilegível.
  • No Deno 2, uma capacidade negada lança Deno.errors.NotCapable (renomeado do antigo PermissionDenied), separando as recusas de permissão do Deno dos erros comuns do sistema operacional.
  • Desde o Deno 2.5, você pode definir conjuntos de permissões nomeados no deno.json e aplicá-los com -P=nome (ou um conjunto default com um -P isolado), mantendo as flags de privilégio mínimo sob controle de versão.
  • Nada no grafo inicial de importações estáticas é verificado contra o sistema de permissões antes de ser carregado, e --allow-run executa subprocessos fora do sandbox. Essas são as duas formas pelas quais código não confiável escapa.

Por que o Deno é seguro por padrão?

Nada é executado com privilégios ambientes: disco, rede, ambiente e criação de subprocessos permanecem fechados até que você os abra. Essa decisão de design veio diretamente de Ryan Dahl, criador original do Node, que construiu o Deno para reverter o padrão de “acesso total a tudo” do Node. No Deno, dependências não recebem autoridade ambiente própria; no Node, um pacote herda qualquer I/O de sistema que o processo ao seu redor possa alcançar, e essa lacuna é a diferença mais marcante entre os dois runtimes.

O Node passou a ter seu próprio modelo de permissões. Ele chegou de forma experimental no Node 20 sob a flag --experimental-permission, foi marcado como estável na v23.5.0, e o Node 24 descontinuou a grafia experimental em favor do simples --permission. O modelo do Deno ainda é mais profundo: é o padrão em vez de uma flag opcional, e cobre mais classes de capacidades com delimitação mais granular.

Quais são as flags de permissão —allow-* do Deno?

Cada capacidade corresponde a uma flag, e a maioria das flags aceita um argumento de lista de permissões. Uma flag isolada concede tudo naquela classe; um argumento a restringe. Um --allow-net isolado concede acesso a todos os hosts em todas as portas, enquanto --allow-net=api.example.com:443 restringe o programa a exatamente um host e uma porta.

FlagProtegeExemplo delimitadoContraparte deny
--allow-readLeituras no sistema de arquivos--allow-read=./data,config.ini--deny-read
--allow-writeGravações no sistema de arquivos--allow-write=./tmp--deny-write
--allow-netAcesso à rede--allow-net=api.example.com:443--deny-net
--allow-envVariáveis de ambiente--allow-env=PORT,HOST--deny-env
--allow-runSubprocessos--allow-run=git,deno--deny-run
--allow-sysAPIs de informação do sistema--allow-sys=hostname--deny-sys
--allow-ffiBibliotecas nativas--allow-ffi=./lib.so--deny-ffi
--allow-importImportações remotas via HTTPS--allow-import=jsr.io--deny-import

Observe que não existe --allow-hrtime. Essa flag foi removida no Deno 2.0, e APIs de temporização de alta resolução como performance.now() estão sempre disponíveis agora.

Quando um script precisa de uma permissão que você não concedeu, o Deno pausa e solicita interativamente:

┏ ⚠️ Deno requests net access to "deno.com:443".
┠─ Requested by `fetch()` API.
┗ Allow? [y/n/A] (y = yes, allow; n = no, deny; A = allow all net permissions) >

Responda y para conceder uma única vez, n para negar (o que lança Deno.errors.NotCapable), ou A para permitir toda a classe. Em CI, passe as flags de antemão para que nada fique travado em um prompt.

Como o conjunto de flags cresceu: precedência do deny, --allow-sys, --allow-import, wildcard em env

As flags deny chegaram no Deno 1.36 (agosto de 2023), e desde então toda flag --allow-* passou a ter uma contraparte --deny-*. Onde as duas se sobrepõem, é a negação que se aplica, o que permite conceder amplamente e abrir exceções:

deno run --allow-read=. --deny-read=./secrets app.ts

O --allow-sys, que remonta ao Deno 1.26 (outubro de 2022), controla APIs de informação do sistema como Deno.hostname() e Deno.systemMemoryInfo(). A única classe de capacidade genuinamente nova no Deno 2.0 foi o --allow-import, que determina de quais hosts HTTPS o seu código pode buscar módulos em tempo de execução; HTTP puro nunca é permitido, importações estáticas são filtradas automaticamente contra a lista, e nomear os seus próprios hosts substitui o conjunto embutido do Deno em vez de somar a ele. Use --deny-import para bloquear hosts específicos por completo.

O acesso ao ambiente ganhou wildcards de sufixo no Deno 2.1. Em vez de listar cada variável, delimite por prefixo:

deno run --allow-env="AWS_*" main.ts

Declarando permissões no deno.json

Desde o Deno 2.5 você pode definir conjuntos de permissões nomeados no deno.json e aplicá-los com -P=nome (ou --permission-set=nome), mantendo as flags de privilégio mínimo sob controle de versão em vez de redigitá-las a cada execução. As chaves do objeto são os nomes das flags (read, write, net, env, sys, run, ffi, import), como documentado na referência do deno.json:

{
  "permissions": {
    "default": {
      "read": ["./deno.json"],
      "env": true,
      "run": { "allow": ["git"] }
    },
    "process-data": {
      "read": ["./data"],
      "write": ["./data"]
    }
  },
  "tasks": {
    "dev": "deno run -P main.ts"
  }
}

Execute deno run -P=process-data main.ts para o conjunto nomeado, ou deno run -P main.ts para o conjunto default. O Deno 2.5 também adicionou a variável de ambiente DENO_AUDIT_PERMISSIONS: aponte-a para um caminho de arquivo e o Deno acrescenta uma entrada JSONL para cada permissão que o programa toca, tenha esse acesso sido concedido ou recusado. É uma forma rápida de descobrir o que um script realmente precisa.

A API de Permissões em tempo de execução

Consulte as permissões no código antes de uma operação restrita para falhar de forma elegante em vez de quebrar com um erro NotCapable. O Deno.permissions expõe query, request e revoke, cada um recebendo um descritor como { name: "net", host: "example.com" }:

const desc = { name: "net", host: "example.com" } as const;

let status = await Deno.permissions.query(desc); // "prompt" | "granted" | "denied"
if (status.state === "prompt") {
  status = await Deno.permissions.request(desc); // triggers the y/n/A prompt
}

if (status.state === "granted") {
  await fetch("https://example.com");
}

await Deno.permissions.revoke(desc); // drop it again

O query informa o estado atual sem solicitar nada, o request solicita ao usuário se o estado ainda é prompt, e o revoke devolve uma capacidade. Isso permite que programas de longa duração verifiquem antes de acessar um recurso e sigam um caminho diferente quando o acesso não está disponível.

As duas armadilhas: importações e --allow-run

Dois comportamentos permitem que código não confiável contorne o sandbox, e vale a pena internalizar ambos. Primeiro, tudo que o Deno consegue resolver estaticamente a partir do seu ponto de entrada (arquivos locais, pacotes npm e JSR, e URLs remotas escritas como literais de string) é buscado antes que o sistema de permissões tenha voz, então uma dependência pode ler seu próprio código-fonte e alcançar a rede antes que a sua primeira flag --allow-* sequer se aplique. Esse passe livre cobre apenas o carregamento e nada mais: no momento em que o código executa, cada operação é verificada novamente. O --allow-import delimita quais hosts remotos podem ser importados, mas não faz com que as importações em si exijam uma concessão em tempo de execução, portanto audite código de terceiros antes de incorporá-lo.

Segundo, o --allow-run é a armadilha mais afiada: o que você cria se torna um processo por direito próprio, carregando os privilégios que o sistema operacional lhe concede em vez do conjunto restrito que você entregou ao Deno. Isso significa que --allow-run=deno permite que um script em sandbox relance o Deno com --allow-all e escape completamente. Além disso, ele restringe apenas qual executável é executado, não os seus argumentos: --allow-run=cat permite que o código leia qualquer arquivo via cat. Delimite-o a binários confiáveis específicos, como --allow-run=git, e observe que o --allow-ffi carrega a mesma classe de risco, já que bibliotecas nativas executam como código de máquina fora das verificações da camada JavaScript.

A postura prática: conceda a lista de permissões mais restrita que funcione, adicione --deny-* sobre caminhos sensíveis, e trate --allow-run e --allow-ffi como fronteiras de confiança, não como conveniências. Comece com zero permissões, execute o script e adicione de volta exatamente o que os prompts (ou o log do DENO_AUDIT_PERMISSIONS) indicarem que ele precisa.

Perguntas Frequentes

Qual é a diferença entre responder 'não' a um prompt de permissão do Deno e o Deno.errors.NotCapable?

São o mesmo resultado a partir de pontos de entrada diferentes. Quando você responde 'n' a um prompt interativo, ou executa sem a flag necessária, a operação negada lança Deno.errors.NotCapable no Deno 2 (renomeado do antigo PermissionDenied). A renomeação permite distinguir as recusas de permissão do próprio Deno de erros comuns do sistema operacional, como um arquivo inexistente, já que ambos anteriormente apareciam como falhas de aparência semelhante.

O --allow-net=example.com também permite HTTPS na porta 443?

Sim. Quando você especifica um host sem porta, como --allow-net=example.com, o Deno permite conexões a esse host em qualquer porta, incluindo a 443. Para restringir a uma única porta você precisa escrevê-la explicitamente como --allow-net=example.com:443, o que então bloqueia todas as outras portas desse host. Um --allow-net isolado, sem argumento, concede acesso a todos os hosts em todas as portas.

Posso combinar --allow-read com --deny-read em caminhos que se sobrepõem?

Sim, e o deny sempre vence. Executar --allow-read=. --deny-read=./secrets concede acesso de leitura a todo o diretório do projeto, exceto ./secrets, que permanece ilegível. As flags deny têm precedência sobre as flags allow em todas as classes de capacidade, então esse padrão permite conceder amplamente e excluir caminhos sensíveis em vez de enumerar cada arquivo permitido individualmente.

Preciso de --allow-import para usar pacotes npm ou JSR?

Não, não para pacotes importados estaticamente. Tudo que o Deno consegue resolver a partir do seu ponto de entrada sem executar código, incluindo pacotes npm e JSR, é buscado antes que o sistema de permissões seja consultado. O --allow-import decide de quais hosts HTTPS as importações remotas podem vir, e HTTP puro nunca é uma opção. Um especificador computado em tempo de execução é diferente: uma URL remota dinâmica precisa de --allow-import, e um caminho local dinâmico precisa de --allow-read.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.