Documentation
Docs
Introduction
Getting Started
Tutorial: build an app
Core Concepts
Routing
Server-Side Rendering
Mimir, state management
Pages & Layouts
API Routes
Styling & Theming
Building & Deploying
API Reference
Guides
Recipes
FAQ (use cases)
Permissions
EkkoJS is deny-by-default. A rune app cannot touch the filesystem, open network sockets, read environment variables, load native code, or spawn processes unless you grant the capability. rune inherits this directly, your pages, API handlers, and SSR code all run inside the same sandbox.
Granting capabilities
Two equivalent ways, additive (the runtime grants the union):
On the command line:
In ekko.json:
For a deployed service, declaring them in ekko.json keeps the systemd unit's ExecStart minimal and the
grants versioned with the code.
The categories
The runtime's valid categories are exactly fs, net, crypto, process, env, ffi, and all. Any
other key in permissions (or --allow) produces an "unknown permission category" warning and grants
nothing.
| Category | Grants | A rune app typically needs it for |
|---|---|---|
fs | Filesystem read/write/stat/watch/symlink | Reading styles/global.scss, templates, static files, content data |
net | HTTP server, fetch, TCP, WebSocket | Binding the listening port; outbound calls to other services |
crypto | Hash, HMAC, encrypt/decrypt, sign/verify, PBKDF2, random | Password hashing, session tokens, signing cookies |
process | Spawning child processes | Rare in web apps |
env | Environment variables | Reading PORT, secrets |
ffi | Loading native libraries | Rare |
all | Everything above | Trusted / dev contexts only |
A minimal SSR site needs fs + net; add env if you read PORT or secrets, and crypto if you hash
passwords or sign sessions (the Authentication recipe needs crypto).
Scoped grants
A blanket fs: true is convenient but broad. You can restrict each category:
- fs accepts a glob or list of globs; only matching paths are readable/writable.
- net accepts
host:portpatterns ("api.example.com","localhost:*"). - env accepts a list of variable names.
The same scoping works on the CLI: --allow=fs:./content,net:localhost:*,env:PORT.
How checks work
Every native call that touches a guarded resource asks the runtime check_permission(category) (and, for
the filesystem, check_permission_path(category, path)):
- Path normalization prevents
../escapes: the path is absolutized,./..are collapsed lexically,
the deepest existing ancestor is canonicalized (resolving symlinks), and the result is matched against your
scoped grants. You cannot scope fs: "./data" and then read ./data/../secrets.
- Protected paths are always denied, even with
fs: true: the runtime binary's directory and the package
store. App code cannot tamper with the runtime or its dependencies.
If a check fails, the call throws a clear error naming the missing capability:
PermissionDenied: net access to "api.example.com:443" is not allowed (grant with --allow=net or net:api.example.com)Why this matters for a web app
The whole app, your code and every dependency, runs under the same grant. A compromised or careless package
cannot quietly read ~/.ssh, phone home, or exfiltrate environment secrets, because it has no ambient
authority; it only has what you declared. When auditing a rune app's blast radius, you read one permissions
block, not the entire dependency tree.
A sensible production grant
Then the deployment just runs ekko run server.tsx (or --allow=fs,net,env if you keep it on the CLI). See
Production deploy.
Next: the naming and folder rules rune relies on, Conventions.