Run Deno scripts with scoped permissions instead of --allow-all
Deno blocks file, network, and environment access by default. Grant only the permissions your script needs.
30 Nov 2025, 06:35 UTC

A missing permission stops a Deno script before it starts
You write a Deno automation script that reads a configuration file and calls an internal API. It works on your laptop, but in CI it crashes with PermissionDenied, or worse, it hangs waiting for an interactive prompt that nobody can answer. The cause is not a bug in your code. It is Deno's secure default: scripts run in a sandbox and cannot touch the file system, network, or environment variables unless you explicitly allow them.
The practical takeaway is simple: grant the narrowest permission flags your script needs, test for PermissionDenied before you deploy, and avoid --allow-all in continuous integration.
What Deno blocks by default
Deno executes code in a secure sandbox. By default, a script cannot:
- Read or write files on the host
- Make outgoing network requests
- Read environment variables
- Run subprocesses
These restrictions apply to every imported module too. If your script pulls in a third-party library, that library starts with the same empty permission set.
Grant scoped permissions with flags
Use command-line flags to open only the doors your script requires. Common flags include:
--allow-read=/path/to/directory— read files only under that directory--allow-net=https://api.example.com— connect only to that origin--allow-env=FOO,BAR— read only those environment variables--allow-run— spawn subprocesses
Multiple flags can be combined. Omitting a path or origin after a flag usually allows all access in that category, which is broader than necessary.
Worked example: a config reader and API caller
Create a file named fetch-config.ts:
import { serve } from 'https://deno.land/std/http/server.ts';
const configPath = Deno.env.get('CONFIG_PATH') ?? './config.json';
const text = Deno.readTextFileSync(configPath);
const config = JSON.parse(text);
const response = await fetch(config.apiUrl);
const data = await response.json();
console.log(data);
Run it without any permission flags:
# Run in a terminal or CI step where the user can execute deno
denorun fetch-config.ts
Expected result: Deno throws Deno.errors.PermissionDenied when it reaches Deno.env.get or Deno.readTextFileSync, because neither environment nor file access was granted. In a non-interactive CI runner, this fails the job immediately rather than hanging.
Re-run with scoped flags:
# Placeholders: replace /app/config and https://api.internal.example.com
denorun --allow-env=CONFIG_PATH --allow-read=/app/config --allow-net=https://api.internal.example.com fetch-config.ts
Expected result: the script reads /app/config/config.json and calls only the allowed origin. If the script tries to read /etc/passwd or call a different domain, Deno throws PermissionDenied.
Risk: --allow-all disables the sandbox entirely. Do not use it in CI or production just to avoid debugging missing flags.
Limitation: permissions are process-wide, not per-module
Deno does not assign permissions to individual modules. When you run --allow-net=https://api.example.com, every dependency imported by your script can also call that origin. A compromised or careless dependency inherits the same network access as your own code.
Practical check: review your deno.json or import map before adding broad flags. Prefer specific origins and paths over wildcards, and audit dependencies the same way you would audit any code running with network access.
Verify before you merge
- Run the script in CI with no flags and confirm it fails with
PermissionDenied, not a silent empty result. - Add only the flags required for the files, hosts, and variables the script actually touches.
- Run again and confirm the script succeeds, then deliberately point it at a disallowed path to confirm Deno blocks it.
If a required permission is missing at runtime, Deno may prompt interactively. That behavior is useful locally but can block pipelines, so set flags in advance rather than relying on prompts.
Closing
Deno's permission system is a guardrail, not a feature to disable for convenience. Start from the default sandbox, add scoped flags, and treat PermissionDenied as a signal that your access model needs attention.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.