Fine‑Grained Permissions in Deno: Declare, Enforce, and Test Secure Access
Deno’s permission system lets you grant fine‑grained access to files, network, env, and more. Learn how to declare flags, request permissions at runtime, and verify them with real examples – plus common pitfalls to avoid.
18 Oct 2026, 14:03 UTC

Fine‑Grained Permissions in Deno
When you run a Deno script, it starts in a sandbox with no filesystem, network, or environment access. The only way to lift those restrictions is by explicitly granting permissions. This article shows the exact flags you need, how to request permissions at runtime, and how to verify that they work. It also highlights common mistakes that can silently bypass the sandbox.
Declaring Permissions on the Command Line
Deno’s most common way to grant access is through command‑line flags. Each flag corresponds to a resource type:
--allow-read– read files or directories--allow-write– write files or directories--allow-net– network sockets--allow-env– environment variables--allow-run– spawn child processes--allow-hrtime– high‑resolution timers (useful for benchmarking)--allow-plugin– load WebAssembly or Deno plugins--allow-all– grant everything (use sparingly)--allow-none– keep the sandbox intact (default)
Permissions can also be scoped to specific paths or hosts. For example:
deno run --allow-read=/etc/passwd --allow-net=example.com,api.example.org script.ts
In a project, you can persist these flags in deno.jsonc:
{
"allow": {
"net": ["example.com", "api.example.org"],
"read": ["./config.json"],
"env": ["API_KEY"]
}
}
Running deno run script.ts will automatically apply the permissions defined in the file.
Runtime Permission Requests
Sometimes you need to ask for a permission after the process has started, such as when a user chooses to load a file. Use the asynchronous Deno.permissions.request API:
async function readConfig() {
const status = await Deno.permissions.request({
name: 'read',
path: './config.json'
});
if (status.state !== 'granted') {
throw new Error('Read permission denied');
}
const data = await Deno.readTextFile('./config.json');
return JSON.parse(data);
}
Key points:
- Always await the promise; otherwise the file read may proceed before permission is granted.
- The returned
statusobject has astateofgranted,prompt, ordenied. - Permissions requested at runtime are per‑process. They do not automatically propagate to child processes.
Host‑Specific Network Access
Deno lets you whitelist specific hosts to reduce the attack surface. The flag syntax is comma‑separated:
deno run --allow-net=example.com,api.example.org script.ts
In the script, a fetch to https://example.com will succeed, but a request to https://google.com will throw a PermissionDeniedError:
try {
await fetch('https://google.com');
} catch (err) {
console.error(err.message); // "PermissionDeniedError: Permission denied: net"
}
Omitting the host list (e.g., --allow-net) grants access to all hosts.
Revoking and Querying Permissions
During runtime you can revoke a permission that was previously granted:
await Deno.permissions.revoke({ name: 'read', path: './config.json' });
After revocation, any attempt to read config.json will fail:
try {
await Deno.readTextFile('./config.json');
} catch (err) {
console.error('Read after revoke:', err.message);
}
To check the current state without modifying it, use Deno.permissions.query:
const status = await Deno.permissions.query({ name: 'read', path: './config.json' });
console.log(`Read permission is ${status.state}`);
Remember that query reflects the state at the time of the call. If you change permissions after querying, re‑query to get fresh data.
Common Pitfalls and Best Practices
- Using
--allow-allremoves the sandbox entirely. Only use it for trusted code or during rapid prototyping. - Runtime
requestcalls must be awaited. Skippingawaitleads to unhandled promise rejections and silent failures. - When specifying host lists, be careful with commas. A typo like
example.com,api.example.org(missing space) is fine, butexample.com, api.example.org(with a space) may be interpreted literally and cause a mismatch. - Child processes launched with
--allow-rundo not inherit the parent’s permissions unless you pass the same flags to them. - Permission status returned by
querycan be stale; always re‑query after arequestorrevoke. - When using
--allow-env, only the listed variables are exposed. Omit the list to allow all environment variables.
Verifying Permissions in Practice
Below is a quick test you can run to confirm that permissions work as expected. Create a file test.txt with any content.
- Read with permission:
deno run --allow-read test.js– the script should output the file contents. - Run the same script without
--allow-read– you should see aPermissionDeniedError. - In
test.js, add a runtime request:await Deno.permissions.request({name:'read', path:'test.txt'});and run without flags. The script should prompt and, upon granting, read the file. - After revoking with
await Deno.permissions.revoke({name:'read', path:'test.txt'});, attempt to read again and confirm the error.
These steps validate that your permission declarations are effective and that your runtime API usage behaves correctly.
Conclusion
Fine‑grained permissions give you precise control over what a Deno script can do, keeping the default sandbox safe. By declaring permissions on the command line or in deno.jsonc, requesting them at runtime, and carefully revoking or querying them, you can write secure, auditable code without sacrificing functionality.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.