Repository navigation
Sandbox and Permissions
The model: you trust what you install, like a VS Code extension, but with limits enforced by the runtime, and trust is explicit. No Docker, no VM: safety comes from what the runtime and the API allow.
An extension runs in its own process: a Node child of the Devlog binary
(ELECTRON_RUN_AS_NODE) started with Node's permission model
(--permission), allowed to read exactly one file, the host script. Its
code is sent over the message channel and evaluated, so it needs no file
access to load.
| An extension… | Because |
|---|---|
| cannot touch files | The permission model allows reading only the host script. Its own folders are reached through ctx.files, a broker in the app. |
| cannot start programs, load native code or spawn workers | The permission model denies them. Node built-ins can be required, but the calls fail with ERR_ACCESS_DENIED. |
cannot require packages |
Only Node built-ins resolve; anything else throws Extensions are bundled: cannot load "…". |
| sees the devlog only through its grant | Every ctx.devlog call is filtered by the app. |
| cannot reach other extensions, the app's windows or Electron | Each has its own message channel to the app and nothing else. (The exception: sending a timesheet through another extension's destination, with permissions.send, through the app.) |
| cannot take the app down | A crash kills only its process. The heap is capped at 256 MB; the environment is stripped to a few locale variables (TZ, LANG, LC_ALL, and SystemRoot/windir on Windows); one that does not start within 30 seconds is stopped; calls that take too long fail. |
| shows UI only in sandboxed frames | Views run in sandbox="allow-scripts" frames with a CSP that forbids network, frames and forms (Views and UI). |
The network is not restricted. An extension can make any request with
fetch; the domains it declares in permissions.network are shown at
consent but not enforced. So an extension can send out whatever it can read,
which is one reason read access is scoped.
Electron's utilityProcess silently ignores --permission, hence the Node
child; the builds must keep Electron's RunAsNode fuse on. The test suite
runs a probe extension (tests/fixtures/extensions/probe) in that process
and checks that file access, child processes and workers are refused.
Declared in the manifest, shown before the extension first runs:
| Permission | The user chooses | Effect |
|---|---|---|
read: true |
A read scope: the whole devlog, or chosen canvases (each with everything inside) |
days, blocks, search, todos, range, activity, onBlockAdded and command block context work within the scope. |
write: true |
A write scope, the same way |
addBlock, editBlock, promote, createCanvas, updateCanvas work within the scope. Top-level canvases need write access to the whole devlog. |
network: [...] |
— | Shown only. |
send: true |
— | May send timesheets through other extensions' destinations (1.7). |
unrestricted: true |
Whether to tick I trust it | Runs outside the sandbox (below). |
What an extension gets with no grant at all: its own settings, secrets and files; the canvas fields it declared (for canvases it can list); its destinations receiving sheets the user sends; its views, commands and providers.
-
canvases()lists canvases in the read or write scope plus their ancestors (so they can be named), plus canvases it keeps; nothing else. -
search,todosandrangereturn nothing outside the read scope (and empty results with no read grant). -
days,blocksandactivitythrow outside it. - Writes outside the write scope fail.
- Command contexts leave out canvases it cannot see, and blocks on canvases it cannot read.
-
activityshows task time on unreadable canvases ascanvasId: null, and window titles only with read access to the whole devlog. - Canvases it keeps (
managedCanvas) are its own whatever the grant.
- Consent is keyed by the devlog folder and stored on the machine, never in the repository, so nothing arriving through git can widen it.
- A new build of a downloaded extension (or any change to a development folder) asks again. A built-in keeps its grant and trust when the app brings a new build.
- Changing a grant restarts the extension; revoking stops it until allowed
again; Remove takes it out of
devlog.jsonand forgets consent here.
An extension that needs what the sandbox forbids (start a platform helper, read files) declares:
"permissions": { "unrestricted": true }It runs only if the user ticks I trust it when allowing it (otherwise:
… runs unrestricted; it can only run if you say you trust it), and then runs
without the permission model: full file-system and process access, the
app's environment, and ctx.packageDir (its own unpacked folder, for scripts
it ships). The API calls are still filtered by its grant, but nothing stops
it reading the repository directly, so ask for it only when you must.
devlog-focus (window tracking) is one.
ctx.files.repo and ctx.files.local go through ExtensionFileStore
(packages/core/src/node/extensionFiles.ts), which:
- takes relative,
/-separated paths only and refuses absolute paths, drive letters,./.., backslashes, reserved characters, Windows device names and alternate data streams; - refuses to go through a symlink anywhere between the devlog root and the target (git can commit links);
- writes atomically (temp file, then rename) and serialises writes;
- caps sizes: 16 MB per file, 256 MB per folder, 20 000 files.
ctx.secrets values are stored per extension in user data, encrypted with
the OS keychain (Electron safeStorage). They are never written to the
devlog, so a leaked or public devlog repository leaks no credential. Each
machine has its own copy; the user enters them on each machine.
Blocks an extension adds carry ext=<id> in their marker and are automatic
blocks: read-only in the app (a todo can still be ticked), shown with a
muted brace. They go through the same data layer as the app
(@devlog/core), so an extension cannot break the file format (marker
escaping, append-only files, image paths). They are ordinary Markdown in the
day files and survive the extension going away.
- GitHub and URL extensions are pinned by SHA-256 in
devlog.lock.json, so every machine runs byte-identical code. - Zips are capped at 20 MB, unpacked contents at 100 MB.
- Unpacked into
userData/extensions/<sha256>/, never into the devlog.
- Enforcing the declared network domains.
- A command to delete an extension's data.
Devlog 0.18.0 · extension API 1.7.0 · storage format 4 · Repository · Design notes
Using Devlog
Writing extensions
- Overview
- Quickstart
- Manifest
- API Reference
- Types
- Views and UI
- UI Kit Reference
- Timesheet Destinations
- Activity and Time Data
- Sandbox and Permissions
- Testing
- Built-in Extensions
- API Versions
- Troubleshooting
Devlog internals