Least‑Privilege Deno Network Services: Permissions, Sandboxing, and Operational Checks
Run a Deno HTTP server with only the permissions it needs. Learn how to scope network and file access, test the sandbox, and know when to change the design for production.
09 Nov 2025, 20:26 UTC

Why Permissions Matter for Deno Network Services
Deploying a Deno HTTP server that listens on localhost:8080 is a common use case. If the process runs with full access to the file system, environment, or network, a compromised dependency or a malicious request can turn the server into a pivot point. Deno’s permission system lets you restrict the process to only the resources it needs, turning the runtime into a lightweight sandbox.
Requirements for a Minimal, Least‑Privilege Service
- Only read from a dedicated data directory (e.g.,
/app/data). - Allow outbound connections exclusively to
localhost:8080(the service’s own listening address). - No environment variables, no write access, no subprocesses.
These constraints translate to the following CLI flags:
deno run \
--allow-net=localhost:8080 \
--allow-read=/app/data \
server.ts
Smallest Suitable Design: Scoped Allowlists
Instead of blanket --allow-net or --allow-read, use scoped allowlists to narrow the attack surface:
--allow-net=localhost:8080– permits only requests to the local service endpoint.--allow-read=/app/data– grants file read access only within the data folder.
Because Deno evaluates permissions at the moment of the operation, a dependency that constructs a dynamic URL or file path will still be checked against the allowlist. For example, the following code will fail if the path is not in the allowlist:
const file = Deno.readTextFile('/etc/passwd'); // PermissionDenied
Runtime Checks and Dynamic Paths
Permission checks occur on each sensitive call. If a module builds a path like ../config.json at runtime, Deno verifies that the resolved absolute path matches the allowlist. This behavior prevents path traversal attacks that rely on runtime path construction.
Trust and Data Boundaries
All third‑party modules imported into server.ts inherit the permissions granted to the entry point. Therefore:
- Never add
--allow-writeunless you trust every dependency to respect the write path. - Do not expose
--allow-envunless you can guarantee that no sensitive variables are read or overwritten. - Consider pinning dependencies with a lockfile or vendoring them to avoid supply‑chain compromise.
Operational Checks
Record the Deno version before deployment:
deno --versionStore the output; permission flag syntax can change between releases.
Verify the allowlist syntax for your installed version:
deno help runConfirm that
--allow-netaccepts host:port syntax and that--allow-readaccepts path prefixes.Test the server in a staging environment using the command below. It should reject outbound requests to external hosts:
deno run \ --allow-net=localhost:8080 \ --allow-read=/app/data \ server.tsFrom another terminal, run:
curl http://localhost:8080 curl http://example.comThe first succeeds; the second returns a
PermissionDeniederror.Validate file access by attempting to read a file outside the allowlist:
deno run \ --allow-net=localhost:8080 \ --allow-read=/app/data \ read.tsInside
read.tstry to read/etc/passwdand confirm that Deno emits a permission error.
Failure Modes and Recovery
- PermissionDenied – the process logs the error and can exit gracefully or retry with a fallback.
- Uncaught Exceptions – wrap sensitive operations in
try/catchto prevent the server from crashing. - Missing Allowlist – if a required path is omitted, the service will fail to start. Use
--allow-allonly for debugging; never in production.
When to Re‑Design
Consider changing the permission strategy if:
- Your service needs to write logs or temporary files. Add
--allow-writewith a scoped path, e.g.,--allow-write=/app/logs. - It must read environment variables (e.g., a secret). Add
--allow-env=SECRET_KEY. - It must spawn subprocesses for external tooling. Add
--allow-run=some-tooland ensure the tool is whitelisted. - It needs to reach other internal services. Expand
--allow-netto include those hosts, or use a proxy within the same sandbox.
Combining Deno Permissions with OS‑Level Isolation
Deno’s sandbox is a runtime guard, not a full OS container. For high‑assurance deployments:
- Run the Deno process inside a lightweight container (Docker, Podman) with a non‑root user.
- Use user namespaces or seccomp to restrict system calls further.
- Place the container behind a reverse proxy that enforces TLS and rate limiting.
Practical Checklist Before Production
| Item | Action | Verification |
|---|---|---|
| Deno version | Record deno --version | Compare against target environment |
| Permission syntax | Run deno help run | Confirm flag support |
| Scoped allowlist | Use --allow-net=localhost:8080 and --allow-read=/app/data | Test access and denial cases |
| Dependency trust | Pin deps, lockfile, vendoring | Audit lockfile integrity |
| OS isolation | Container, non‑root user | Run id inside container |
Conclusion
By combining scoped Deno permission flags with minimal OS isolation, you can run a network service that only has the exact capabilities it needs. This approach reduces the blast radius of compromised dependencies, limits the impact of misconfigurations, and provides a clear audit trail of what the process is allowed to do.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.