Fix: Deno PermissionDenied — Missing --allow-read, --allow-net, and Other Flags
Quick Answer
How to fix Deno PermissionDenied (NotCapable in Deno 2) errors — the right permission flags, path-scoped permissions, deno.json permission sets, and the Deno.permissions API.
The Error
You run a Deno script and it immediately crashes:
error: Uncaught PermissionDenied: Requires read access to "./config.json", run again with the --allow-read flag
at Object.readTextFileSync (ext:deno_fs/30_fs.js:732:9)
at file:///home/user/app/main.ts:3:1Or it fails on a network request:
error: Uncaught (in promise) PermissionDenied: Requires net access to "api.example.com:443", run again with the --allow-net flag
at mainFetch (ext:deno_fetch/26_fetch.js:195:9)
at file:///home/user/app/main.ts:7:1Or it crashes when reading an environment variable:
error: Uncaught PermissionDenied: Requires env access to "HOME", run again with the --allow-env flag
at Object.get (ext:runtime/30_os.js:85:16)
at file:///home/user/app/main.ts:5:1In Deno 2 (released October 2024), the same errors appear as NotCapable rather than PermissionDenied:
error: Uncaught (in promise) NotCapable: Requires write access to "./output/", run again with the --allow-write flagAll of these mean the same thing: your script tried to access a resource that Deno’s security sandbox blocked.
Why This Happens
Deno is secure by default. Unlike Node.js or Bun, a Deno script can’t read files, make network requests, or access environment variables unless you explicitly grant it permission to do so via command-line flags.
This is intentional. A script you download from the internet can’t quietly exfiltrate your SSH keys or phone home to an external server — unless you run it with --allow-read and --allow-net. That’s the design.
The permission system covers eight areas:
| Flag | What it controls |
|---|---|
--allow-read | File system reads |
--allow-write | File system writes |
--allow-net | Network access (TCP, fetch, WebSocket) |
--allow-env | Reading environment variables |
--allow-run | Spawning subprocesses |
--allow-sys | System info (hostname, memory, OS) |
--allow-ffi | Native library loading |
--allow-import | Remote HTTP/HTTPS module imports |
If your script triggers any of these without the corresponding flag, Deno throws PermissionDenied (or NotCapable in Deno 2) and exits.
Fix 1: Add the Correct Permission Flag
The simplest fix is to pass the missing flag when running your script.
Read access:
deno run --allow-read main.tsNetwork access:
deno run --allow-net main.tsEnvironment variables:
deno run --allow-env main.tsMultiple permissions at once:
deno run --allow-read --allow-net --allow-env main.tsGrant all permissions (for development or trusted scripts):
deno run --allow-all main.ts
# or the shorthand:
deno run -A main.tsThe error message always tells you which flag to add — read it carefully. For Requires net access to "api.example.com:443", add --allow-net. For Requires env access to "DATABASE_URL", add --allow-env.
Common Mistake: Using -A everywhere because it stops all permission errors feels like a shortcut, but it defeats the entire point of Deno’s security model. Any dependency in your project — including transitive ones you didn’t install directly — gets full access to your filesystem and network. Use -A during exploratory development, then tighten to specific flags before committing or deploying.
Fix 2: Scope Permissions to Specific Paths and Hosts
Using --allow-read with no argument grants read access to the entire filesystem. That works, but it’s broader than necessary. Every permission flag accepts an optional scope that limits what it applies to.
Restrict reads to specific directories:
# Only allow reading from ./config and ./data
deno run --allow-read=./config,./data main.tsRestrict network access to specific hosts:
# Only allow requests to these two hosts
deno run --allow-net=api.example.com,cdn.example.com main.ts
# With port
deno run --allow-net=127.0.0.1:8080 main.ts
# Wildcard subdomains
deno run --allow-net=*.example.com main.tsRestrict environment variable access:
# Only allow reading DATABASE_URL and NODE_ENV
deno run --allow-env=DATABASE_URL,NODE_ENV main.tsRestrict subprocess execution:
# Only allow running git and curl
deno run --allow-run=git,curl main.tsNote: --allow-run only controls what Deno permits at the sandbox level. The subprocess itself still needs OS-level execute permission. If you’re hitting bash: ./script.sh: Permission denied inside a subprocess, that’s a separate issue — see bash permission denied for OS-level fixes like chmod +x.
Pro Tip: Scoped permissions are production hygiene, not just security theater. If your script only needs to read ./config.json, grant --allow-read=./config.json. If a compromised dependency later tries to read ~/.ssh/id_rsa, Deno blocks it automatically.
Fix 3: Configure Permissions in deno.json Tasks
Running long permission flags manually every time gets tedious. Put them in deno.json under tasks:
{
"tasks": {
"dev": "deno run --allow-net --allow-read=./src --allow-env=DATABASE_URL,PORT src/main.ts",
"start": "deno run --allow-net --allow-read=./src --allow-env src/main.ts",
"test": "deno test --allow-read=./src,./tests --allow-env=TEST_DATABASE_URL"
}
}Then run:
deno task dev
deno task start
deno task testThis locks in your permission requirements alongside the command, so the whole team runs with the same flags. If you’re deploying Deno code to Cloudflare Workers via Wrangler, permissions work differently — the Workers runtime has its own access model. See Wrangler not working for Cloudflare-specific configuration issues.
Fix 4: Use Permission Sets in deno.json (Deno 2.5+)
Deno 2.5 added a permissions field to deno.json that lets you define named permission sets and reference them with -P:
{
"permissions": {
"default": {
"read": ["./src", "./deno.json"],
"net": ["api.example.com"],
"env": ["DATABASE_URL", "PORT"]
},
"dev": {
"read": true,
"write": true,
"net": true,
"env": true,
"run": ["deno", "git"]
},
"test": {
"read": ["./src", "./tests"],
"env": ["TEST_DATABASE_URL"]
}
}
}Use them at runtime:
# Uses the "default" permission set
deno run -P main.ts
# Uses the "dev" permission set
deno run -P=dev main.ts
# Uses the "test" permission set
deno test -P=testYou can also mix allow and deny within a set:
{
"permissions": {
"safe": {
"read": {
"allow": ["./src"],
"deny": ["./src/secrets"]
}
}
}
}This is the cleanest way to codify your security requirements in larger projects — the permission policy lives in the repo, not in tribal knowledge or shell scripts.
Fix 5: Check Permissions Programmatically
If your script needs to behave differently depending on what permissions it has, use the Deno.permissions API to query the current state before attempting an operation:
// Check before reading
const readStatus = await Deno.permissions.query({ name: "read", path: "./config.json" });
if (readStatus.state === "granted") {
const config = await Deno.readTextFile("./config.json");
// use config
} else {
// Fall back to defaults
console.warn("No read access — using default config");
}Request permission interactively (prompts the user in a terminal):
const status = await Deno.permissions.request({ name: "net", host: "api.example.com" });
if (status.state === "granted") {
const res = await fetch("https://api.example.com/data");
// ...
} else {
throw new Error("Network access denied — cannot fetch data");
}Revoke a permission once you no longer need it:
// Read the file, then immediately revoke read access
const data = await Deno.readTextFile("./credentials.json");
await Deno.permissions.revoke({ name: "read", path: "./credentials.json" });
// From this point forward, reading ./credentials.json will failThe query method is the most useful in practice — it lets you write defensive initialization code that degrades gracefully instead of crashing.
Permission descriptor shapes by type:
// Each permission type has a specific descriptor shape
await Deno.permissions.query({ name: "read", path: "./data" });
await Deno.permissions.query({ name: "write", path: "./output" });
await Deno.permissions.query({ name: "net", host: "example.com" });
await Deno.permissions.query({ name: "env", variable: "HOME" });
await Deno.permissions.query({ name: "run", command: "git" });
await Deno.permissions.query({ name: "ffi", path: "./native.so" });
await Deno.permissions.query({ name: "sys", kind: "hostname" });Fix 6: Block Specific Resources with —deny Flags
Deno 2 added --deny-* flags that take priority over any allow flag. This lets you grant broad access and carve out exceptions:
# Allow all env vars except credentials
deno run --allow-env --deny-env=AWS_SECRET_ACCESS_KEY,DATABASE_PASSWORD main.ts
# Allow all reads except the secrets directory
deno run --allow-read --deny-read=./secrets main.ts
# Allow network, but not to internal services
deno run --allow-net --deny-net=169.254.169.254 main.tsNote: Deny flags always win. If you pass both --allow-read=./secrets and --deny-read=./secrets/api-key.json, the file at ./secrets/api-key.json is denied regardless.
This pattern is useful when wrapping third-party scripts or running tools you don’t fully control, where you want to allow broad categories but block specific sensitive paths.
Still Not Working?
Deno 2: NotCapable replaces PermissionDenied
In Deno 2, permission flag violations now raise Deno.errors.NotCapable instead of Deno.errors.PermissionDenied. The error message wording stays the same (“run again with the —allow-read flag”), but if you’re catching errors by type, update your catch blocks:
// Old code (Deno 1):
try {
await Deno.readTextFile("./data.json");
} catch (e) {
if (e instanceof Deno.errors.PermissionDenied) {
console.error("No read permission");
}
}
// Updated for Deno 2:
try {
await Deno.readTextFile("./data.json");
} catch (e) {
if (e instanceof Deno.errors.NotCapable) {
console.error("No read permission");
}
}Deno.errors.PermissionDenied still exists in Deno 2 but is reserved for actual OS-level permission errors (the file exists but the OS user doesn’t have access) — separate from Deno’s security model violations.
--allow-run and Subprocesses with LD_PRELOAD or DYLD_*
Deno 2 added a restriction: if you use a scoped --allow-run=git and the subprocess is launched with LD_PRELOAD or DYLD_* environment variables set, Deno blocks it even if git is in the allow list. You’ll see:
error: Uncaught NotCapable: Requires --allow-all permissions to spawn subprocess with LD_PRELOAD environment variableThe fix is to use unscoped --allow-run (grants subprocess access to any program) or --allow-all. There’s no way to pass a specific LD_PRELOAD subprocess through a scoped --allow-run in Deno 2.
npm Packages That Need Additional Permissions
Most npm packages imported via npm: work without extra permissions. But packages that use native addons (.node files compiled from C++) require --allow-ffi:
deno run --allow-ffi npm:sharp main.tsnpm packages with preinstall/postinstall scripts don’t run by default in Deno. To enable them:
deno run --allow-scripts npm:my-package main.tsWarning: --allow-scripts lets npm lifecycle scripts run arbitrary shell commands. Only use it with packages you trust.
The Prompt Appeared Once but Not Now
By default, Deno prompts you interactively when a permission is needed and stdin is a TTY. If the prompt appeared and you allowed it, that grant is only for the current process — it doesn’t persist between runs.
If you’re running in a CI/CD pipeline and the prompt never appears, it’s because Deno detects a non-TTY environment and treats it as a denial. Add the required flags explicitly to your CI command:
# In CI — no prompt, just fail fast if a flag is missing
deno run --no-prompt --allow-net --allow-env main.ts--allow-hrtime Is Gone in Deno 2
If you’re upgrading from Deno 1, --allow-hrtime no longer exists. Remove it from your commands — it was deprecated in Deno 1.x and removed in Deno 2. High-resolution timing via performance.now() works without it.
deno compile Embeds Permissions at Compile Time
When you compile a Deno script with deno compile, the permission flags you pass become part of the binary. The compiled executable will only ever have those permissions — users can’t add more at runtime.
# Compile with network and env access
deno compile --allow-net=api.example.com --allow-env=API_KEY --output myapp main.ts
# Running the binary — no flags needed, no more can be added
./myappIf you compile without a needed permission and the binary crashes with PermissionDenied, you must recompile with the correct flag. There’s no way to grant additional permissions to an already-compiled Deno binary.
Checking Which Permissions Your Script Actually Needs
If you’re not sure what permissions a script requires, run it with -A first and watch the error messages to discover the minimum set. Once you know what it accesses, replace -A with the specific flags.
Alternatively, use deno info to inspect your script’s module graph and external dependencies before running it:
deno info src/main.tsThis shows all imported modules (including npm packages) so you can reason about what external access they might require before executing anything.
If you’re migrating an existing Node.js project to Deno, the node cannot find module article covers compatibility shims and module resolution differences you’ll likely hit alongside the permission errors.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: jose JWT Not Working — Token Verification Failing, Invalid Signature, or Key Import Errors
How to fix jose JWT issues — signing and verifying tokens with HS256 and RS256, JWK and JWKS key handling, token expiration, claims validation, and edge runtime compatibility.
Fix: Fastify Not Working — 404, Plugin Encapsulation, and Schema Validation Errors
How to fix Fastify issues — route 404 from plugin encapsulation, reply already sent, FST_ERR_VALIDATION, request.body undefined, @fastify/cors, hooks not running, and TypeScript type inference.
Fix: Vinxi Not Working — Dev Server Not Starting, Routes Not Matching, or Build Failing
How to fix Vinxi server framework issues — app configuration, routers, server functions, middleware, static assets, and deployment to different platforms.
Fix: Better Auth Not Working — Login Failing, Session Null, or OAuth Callback Error
How to fix Better Auth issues — server and client setup, email/password and OAuth providers, session management, middleware protection, database adapters, and plugin configuration.