Tracing permissions

esrun grants nothing by default, so a deployment has to name what it may reach. The hard part was never the flags — it is knowing which ones you need, and esdev grants everything, so the dev loop never tells you. Run the program once under a trace:

Shell
esdev --trace-permissions app.mjs
TEXT
esdev: the permissions this run used

  read      fs_read
  imports   import
  net       fetch
  env       process_env

  esrun --allow-read --allow-imports --allow-net --allow-env app.mjs

That last line is the one to deploy with.

It reports what was used, not what was granted

The trace watches the capability check itself — the only place that knows what a program reached for, as opposed to what it was handed. So a run under esdev's full grant still tells you the minimum esrun needs.

Refusals are reported too, and left out of the line:

Shell
esdev --trace-permissions --deny-all app.mjs
TEXT
  imports   import  (denied — the program asked and was refused)

  esrun app.mjs

The program asked and did not get it; whether that was the right answer is your call, not a trace's. The report is printed however the run ended — including a process.exit() and a ^C drain, which for a server is every run.

Workers are in the same report

A worker runs on its own thread in its own isolate, and its grants are set at the spawn — the place they are hardest to get right. Anything it reaches for appears in the report alongside the main agent's.

Scopes are not traced

The line grants each capability unnarrowed. Narrowing it — --allow-read=./data, --allow-net=api.example.com — is still yours, and a scope is enforced provider-side rather than at the capability bit. See Securing the runtime.

Then shorten it further

Bundling removes a capability outright: an unbundled program needs --allow-imports so the loader can walk node_modules, and a bundle has no imports left to resolve.

Shell
esdev build app.mjs
esrun --allow-read --allow-net --allow-env dist/app.js
Last updated on
Edit this page