Safeguard
Security Guides

Deno Security Best Practices (2026)

Deno is secure by default, but its permission model only protects you if you use it deliberately. Here is how to run Deno with least privilege and keep its dependency graph clean.

Daniel Osei
Security Researcher
5 min read

Deno's headline security feature is that it is secure by default: a program cannot read files, open network connections, read environment variables, or spawn subprocesses unless you explicitly grant that capability at launch. This is the opposite of Node.js, where any script can do anything the process user can. That default is genuinely valuable, but it only protects you if you resist the temptation to hand over blanket access. The most common Deno security failure in 2026 is reaching for -A (allow-all) the moment something does not work, which throws away the entire model.

The short answer to running Deno securely: grant the narrowest permission flags a program needs, scope them to specific paths and hosts, lock your dependencies with deno.lock, treat npm and URL imports as untrusted third-party code, and never paste -A into production. Everything below expands on that.

Grant least-privilege permissions

Deno's permissions are explicit and, crucially, scopable. Instead of "can this program use the network," you can say "this program may connect only to api.example.com." Scoping is what turns the permission model from a speed bump into a real boundary.

# Bad: full access. The sandbox is now meaningless.
deno run -A server.ts

# Good: only what the program actually needs, scoped tightly.
deno run \
  --allow-net=api.example.com:443 \
  --allow-read=./config \
  --allow-env=API_KEY \
  server.ts

The two permissions to treat with the most suspicion are --allow-run and --allow-ffi. Both are effectively escape hatches out of the sandbox: --allow-run lets the program spawn arbitrary subprocesses, and --allow-ffi lets it call into native libraries with no further checks. Grant them only when unavoidable, and scope --allow-run to specific binaries when you can.

You can also inspect and, where appropriate, revoke permissions at runtime through the Deno.permissions API, which is useful for dropping privileges after startup work is done.

// Drop network access once initial config has been fetched.
const status = await Deno.permissions.query({ name: "net" });
if (status.state === "granted") {
  await Deno.permissions.revoke({ name: "net" });
}

Lock dependencies and pin what you import

Deno imports code from URLs and, in Deno 2, from npm via npm: specifiers. Both are third-party code running in your process, so integrity matters. Commit a deno.lock file: it records the expected content hash of every remote module and npm package, so a tampered or swapped upstream module fails the integrity check instead of executing silently.

{
  "imports": {
    "@std/assert": "jsr:@std/assert@^1.0.0",
    "zod": "npm:zod@^3.23.0"
  },
  "lock": true
}

Prefer JSR or pinned, versioned imports over floating URLs. An import that points at a mutable tag or a "latest" alias can change under you; a version- and hash-pinned import cannot. Run deno install --frozen in CI so a build fails if the lockfile and manifest disagree, rather than resolving something new.

Treat npm compatibility as an expanded attack surface

Deno's npm compatibility is a productivity win and a security consideration at the same time. Pulling in an npm package means pulling in its entire transitive tree, with all the supply chain risk that carries in the Node ecosystem. Deno does not run npm lifecycle install scripts by default, which removes one major infection vector, but the package code itself still runs when imported. A vulnerable or malicious npm dependency is just as dangerous under Deno as under Node once it executes.

Deno hardening checklist

ControlAction
Least privilegeReplace -A with the specific --allow-* flags needed
Scoped accessRestrict --allow-net, --allow-read, --allow-write to exact hosts and paths
Escape hatchesAvoid --allow-run and --allow-ffi; scope them if required
IntegrityCommit deno.lock and run deno install --frozen in CI
Pinned importsUse versioned JSR/npm specifiers, not mutable URLs
Dependency auditScan the full npm and JSR graph for known CVEs
Runtime updatesKeep the Deno runtime patched

How Safeguard helps

Deno's permission model protects the host, but it does nothing about a vulnerable package inside your dependency graph. Safeguard's software composition analysis resolves your npm and JSR dependencies, flags known CVEs, and adds reachability so you prioritize the advisories that touch real code paths. Griffin AI explains whether a finding is exploitable in your specific usage, and autonomous auto-fix opens a tested pull request when a safe upgrade exists. Developers run the same analysis in their editor and CI with the Safeguard CLI, and teams comparing platforms can review the pricing.

Bring continuous dependency analysis to your Deno services: get started free or read the documentation.

Frequently Asked Questions

Is Deno more secure than Node.js?

By default, yes, because Deno denies file, network, environment, and subprocess access unless you grant it explicitly, whereas Node grants everything the process user can do. That advantage disappears the moment you run with -A (allow-all), so the security benefit depends entirely on using scoped permission flags.

What do the allow-run and allow-ffi permissions actually risk?

Both are escape hatches from the sandbox. --allow-run lets a program spawn arbitrary subprocesses, and --allow-ffi lets it call native libraries with no further permission checks, so either can bypass the other restrictions. Grant them only when necessary and scope --allow-run to specific binaries.

Does Deno protect me from malicious npm packages?

Partly. Deno does not execute npm lifecycle install scripts by default, which removes a common infection vector, but the package's own code still runs when you import it. A vulnerable or malicious npm dependency remains dangerous, so you still need dependency scanning.

How do I guarantee my Deno dependencies are not tampered with?

Commit a deno.lock file, which stores a content hash for every remote module and npm package and fails the integrity check if any upstream changes, and use versioned JSR or npm specifiers rather than mutable URLs. Run deno install --frozen in CI so mismatches fail the build.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.