Fine‑Grained Permissions in Deno: Grant, Revoke, and Secure Your Runtime
Learn how Deno’s permission system lets you grant only the access your script needs, revoke privileges at runtime, and avoid common pitfalls. A step‑by‑step example shows how to tighten security after startup.
19 Jul 2025, 01:20 UTC

Why Permissions Matter in Deno
Deno’s default stance is "secure by design": every script starts with no file, network, environment, or child‑process access. This protects you from accidental privilege escalation, but it also means you must explicitly grant the resources your code actually needs. If you forget to grant a required permission, the operation will throw a PermissionDenied error and stop, which can be a useful safety net during development.
Thesis: Fine‑Grained Permissions Give You Control Without Complexity
Unlike many runtimes that rely on a single --allow-all flag, Deno lets you specify exact scopes—such as a single directory or a single host. You can also change permissions while the program is running, tightening the security posture after an initial bootstrap phase. This article walks through how to grant permissions at launch, request additional ones at runtime, and revoke them to close the attack surface.
1. Granting Permissions on the Command Line
Permissions are passed as flags to deno run. The syntax is --allow-resource[:scope]. A scope can be a file path, a directory, or a host. Here’s a typical command that allows network access to api.example.com and read access only to /tmp:
deno run --allow-net=api.example.com --allow-read=/tmp my_app.ts
Running the script without these flags will cause any network or file read outside the allowed scopes to fail.
2. Requesting Permissions at Runtime
Sometimes you don’t know all the resources you’ll need until the program is running. Deno’s PermissionRequest API lets you ask the user (or an automated test harness) for a permission after startup. The API returns a promise that resolves to an object with a state property: granted, prompt, or denied.
async function ensureNetwork() {
const status = await Deno.permissions.request({ name: "net" });
if (status.state !== "granted") {
throw new Error("Network access is required but was denied");
}
}
await ensureNetwork();
// Now you can safely call fetch() or use WebSocket
When running in a terminal, Deno will prompt the user with a yes/no question. In CI environments, you can pre‑grant the permission with --allow-net or use the --quiet flag to skip prompts.
3. Revoking Permissions to Tighten Security
Once a permission is granted, it stays in effect until you explicitly revoke it. Revocation is useful if a script initially needs broad access (e.g., to download a dependency) and then wants to restrict itself for subsequent operations.
// Initially allow all network hosts
await Deno.permissions.request({ name: "net" });
// ...download a module
// Now restrict to api.example.com only
await Deno.permissions.revoke({ name: "net" });
await Deno.permissions.request({ name: "net", host: "api.example.com" });
After the revoke, any attempt to open a network socket to a host other than api.example.com will throw a PermissionDenied error. Note that revocation is not retroactive: code that already performed a network operation before the revoke will still have succeeded.
4. Fine‑Grained Directory Access
Granting read/write access to an entire filesystem is rarely necessary. Deno allows you to limit access to a specific directory tree. The following command grants read access only to /tmp:
deno run --allow-read=/tmp read_files.ts
Any attempt to read /etc/passwd or /home/user/.bashrc will fail. This is a powerful way to sandbox a script that only needs temporary storage.
Concrete Example: A Secure HTTP Client
Below is a minimal TypeScript script that demonstrates the full permission cycle: start with no permissions, request network access at runtime, perform a request, then revoke that permission and attempt a second request to show the denial.
///
async function main() {
// 1. Request network permission
const netStatus = await Deno.permissions.request({ name: "net" });
if (netStatus.state !== "granted") {
console.error("Network permission was not granted.");
return;
}
// 2. Make an allowed request
const res = await fetch("https://api.example.com/data");
console.log("First request status:", res.status);
// 3. Tighten security: revoke net and allow only example.com
await Deno.permissions.revoke({ name: "net" });
await Deno.permissions.request({ name: "net", host: "api.example.com" });
// 4. Attempt a request to a disallowed host
try {
await fetch("https://evil.example.com/malicious");
} catch (e) {
console.log("Second request failed as expected:", e.message);
}
}
main();
Run this script with deno run --allow-net main.ts to allow the initial request. The script will prompt for network access on the first call, then perform the fetch. After revoking, the second fetch to evil.example.com will throw, demonstrating that the runtime permission change took effect.
Trade‑Offs and Limitations
- Revocation is not retroactive. Code that already executed with a permission remains able to use that privilege until the permission is explicitly revoked.
- Prompting can stall non‑interactive workflows. In CI pipelines, always pre‑grant required permissions or use
--quietto suppress prompts. - Over‑permissive flags defeat security. Using
--allow-allor broad--allow-netwithout host restrictions should be avoided in production. - Permission checks are synchronous. The
requestAPI returns a promise, but the actual permission state is resolved immediately, so the overhead is minimal.
Actionable Checklist for Secure Deno Apps
- Start with
--allow-none(the default) or no flags. - Identify the minimal set of resources needed.
- Grant permissions via command line with the most specific scopes possible.
- Use
Deno.permissions.requestfor dynamic needs and handledeniedstates gracefully. - Revoke unnecessary permissions after the bootstrap phase.
- Test the application in a restricted environment to confirm that all required resources are accessible and all others are blocked.
By following these steps, you can build Deno applications that are both functional and resilient against accidental or malicious privilege escalation.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.