# How Deno's Permission System Works: CLI Flags, Runtime Checks, and the JavaScript API

> Understand Deno's permission system. Explore CLI flags, runtime checks, and the JavaScript API for secure script execution in this technical deep dive.

- Repository: [Deno/deno](https://github.com/denoland/deno)
- Tags: internals
- Published: 2026-02-25

---

**Deno isolates script execution by requiring explicit permission grants for privileged actions, enforcing security through a three-layer architecture that spans command-line parsing, a centralized Rust permission store, and a JavaScript query API.**

The `denoland/deno` repository implements a secure-by-default runtime where file system access, network requests, and environment variable reads are denied unless explicitly granted. Unlike Node.js, which provides unrestricted access to system resources, Deno's permission system ensures that every side-effecting capability is gate-kept by a central mechanism. This article examines the source code implementation, from CLI argument parsing in [`cli/args/flags.rs`](https://github.com/denoland/deno/blob/main/cli/args/flags.rs) to the runtime permission checks in [`runtime/permissions.rs`](https://github.com/denoland/deno/blob/main/runtime/permissions.rs).

## The Three-Layer Permission Architecture

Deno's permission model operates through distinct layers that translate user intent into enforced security boundaries.

### CLI Flag Parsing and Initial State

When a Deno process starts, the command-line parser in [`cli/args/flags.rs`](https://github.com/denoland/deno/blob/main/cli/args/flags.rs) processes all `--allow-*` flags (such as `--allow-read`, `--allow-net`, and `--allow-env`). These flags define the initial **Permissions** state seeded to the runtime. If a flag is omitted, the corresponding permission is **denied by default**, establishing the secure-by-default posture before any JavaScript code executes.

### The Runtime Permission Store

At the core of the system lies the `Permissions` struct defined in [`runtime/permissions.rs`](https://github.com/denoland/deno/blob/main/runtime/permissions.rs). This struct maintains the state of each permission as **granted**, **denied**, or **prompt**, and implements the public API exposed to JavaScript. The permission store handles complex logic including:

- Tracking granular permissions (specific paths for `--allow-read`, host patterns for `--allow-net`)
- Managing interactive prompts when the `--prompt` flag is enabled
- Enforcing permission revocation and subsequent re-granting

### JavaScript Permission API and Ops Bridge

The `Deno.permissions` namespace provides the JavaScript façade for the permission system, implemented through ops in [`runtime/ops/permissions.rs`](https://github.com/denoland/deno/blob/main/runtime/ops/permissions.rs). This layer exposes three primary methods:

- **`query`** – Check the current state of a permission without consuming user interaction
- **`request`** – Attempt to grant a permission, triggering an interactive prompt if necessary
- **`revoke`** – Explicitly remove a previously granted permission

These methods bridge JavaScript calls to the underlying Rust `Permissions` struct, ensuring consistent state across the runtime boundary.

## How Privileged Operations Enforce Permissions

When JavaScript code invokes a privileged operation like `Deno.readFile` or `Deno.connect`, the corresponding Rust op checks the permission store before executing the system call. For example, file system operations verify permissions through methods such as `state.read.allow` within the op implementation. If the permission is not granted, the op returns a permission-denied error immediately, preventing unauthorized access.

This enforcement mechanism applies uniformly across all capabilities:

- **File system access** checks `--allow-read` and `--allow-write` permissions
- **Network operations** validate `--allow-net` grants
- **Environment variable access** requires `--allow-env`
- **System information** queries check `--allow-sys`

## Working with Deno Permissions in Practice

### Granting Permissions via CLI

Pass `--allow-*` flags when starting the Deno process to pre-grant capabilities:

```bash

# Allow read access to specific directories only

deno run --allow-read=/tmp,/home/user/data script.ts

# Allow network connections to specific hosts

deno run --allow-net=example.com,api.example.com server.ts

# Allow all environment variable access

deno run --allow-env script.ts

```

### Querying Permission State at Runtime

Scripts can inspect current permissions without triggering prompts using `Deno.permissions.query()`:

```typescript
// script.ts
const status = await Deno.permissions.query({ name: "net" });
console.log(status.state); // "granted", "denied", or "prompt"

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

```

### Requesting Permissions Dynamically

Use `Deno.permissions.request()` to prompt for elevated privileges at runtime (requires `--prompt` flag):

```typescript
// script.ts
const result = await Deno.permissions.request({ name: "env" });
if (result.state === "granted") {
  console.log("HOME directory:", Deno.env.get("HOME"));
} else {
  console.error("Cannot access environment variables");
}

```

### Revoking Previously Granted Permissions

Explicitly remove permissions to reduce the attack surface during sensitive operations:

```typescript
// script.ts
// Revoke read access to a specific file
await Deno.permissions.revoke({ name: "read", path: "./secret.txt" });

// Revoke all network permissions
await Deno.permissions.revoke({ name: "net" });

```

### Using Interactive Prompts

Enable interactive permission requests by starting Deno with the `--prompt` flag:

```bash
deno run --prompt script.ts

```

When the script calls `Deno.permissions.request()`, the runtime displays an interactive terminal prompt asking the user to allow or deny the specific capability.

## Key Source Files in the Deno Repository

The permission system spans several critical files in the `denoland/deno` codebase:

- **[`runtime/permissions.rs`](https://github.com/denoland/deno/blob/main/runtime/permissions.rs)** – Defines the `Permissions` struct, permission-state handling, and the public API used by the JavaScript layer.
- **[`runtime/ops/permissions.rs`](https://github.com/denoland/deno/blob/main/runtime/ops/permissions.rs)** – Implements the ops that expose `query`, `request`, and `revoke` to JavaScript and validates permission state for other system ops.
- **[`cli/args/flags.rs`](https://github.com/denoland/deno/blob/main/cli/args/flags.rs)** – Parses command-line flags (`--allow-*`, `--prompt`) and initializes the permission configuration passed to the runtime.
- **[`runtime/worker.rs`](https://github.com/denoland/deno/blob/main/runtime/worker.rs)** – Instantiates a worker with its own `Permissions` instance, linking the CLI-derived configuration to the runtime environment.

These files collectively implement Deno's fine-grained, explicit permission model, ensuring scripts run with the least privilege required while giving developers full programmatic control over capability grants.

## Summary

- **Deno's permission system** requires explicit opt-in for all privileged operations through CLI flags like `--allow-read` and `--allow-net`.
- The **`Permissions` struct** in [`runtime/permissions.rs`](https://github.com/denoland/deno/blob/main/runtime/permissions.rs) maintains the central state of granted, denied, and promptable capabilities.
- The JavaScript **`Deno.permissions` API** provides `query()`, `request()`, and `revoke()` methods for runtime permission introspection and management.
- All privileged Rust ops check the permission store (e.g., via `state.read.allow`) before executing system calls, returning permission-denied errors for unauthorized access.
- The `--prompt` flag enables interactive permission requests, allowing scripts to request elevated privileges dynamically during execution.

## Frequently Asked Questions

### What happens if I run a Deno script without any --allow flags?

If you execute a Deno script without granting permissions via CLI flags, the runtime operates in a restricted sandbox mode. All file system, network, and environment access is denied by default, and any attempt to use these capabilities throws a permission-denied error. You must either pre-grant permissions with `--allow-*` flags or use the `--prompt` flag to enable interactive permission requests at runtime.

### Can Deno permissions be restricted to specific paths or hosts?

Yes, Deno supports fine-grained permission scoping. You can specify allowed paths when using `--allow-read` or `--allow-write` (e.g., `--allow-read=/tmp,/home/user/data`), and restrict network access to specific hosts with `--allow-net=example.com` or `--allow-net=192.168.1.0/24`. These constraints are enforced by the `Permissions` struct in [`runtime/permissions.rs`](https://github.com/denoland/deno/blob/main/runtime/permissions.rs).

### How do I temporarily revoke a permission after granting it?

Use `Deno.permissions.revoke()` to remove previously granted permissions programmatically. For example, `await Deno.permissions.revoke({ name: "read", path: "./secret.txt" })` removes read access to that specific file. After revocation, subsequent attempts to access the resource will fail until the permission is re-granted either via CLI flags or a new `request()` call with user approval.

### What is the difference between Deno.permissions.query() and Deno.permissions.request()?

`Deno.permissions.query()` inspects the current permission state without user interaction, returning immediately with "granted", "denied", or "prompt". In contrast, `Deno.permissions.request()` attempts to acquire the permission and may trigger an interactive terminal prompt (if using `--prompt`) or return the current state if already determined. Use `query()` for checks, `request()` for acquiring new permissions.