0.1.7
This release adds the charter: the capability policy of a window's own document, enforced by the kernel. It is the middle ground between Electron (where whatever the preload exposes is reachable by anything in the document) and Tauri (where capabilities are a static config file): the charter is declared in TypeScript, applied per window at runtime and revocable.
The charter
window.setCharter/getCharter/clearCharter(control socket) and the SDK surface onBrowserWindow:const win = new BrowserWindow({ charter: { allow: ['ow-window', 'fs:readText'] } }) await win.setCharter({ allow: [charterPresets.shell, 'net'], deny: ['net:download'] }) await win.clearCharter()- Deny by default. Once a window has a charter, every call the kernel did
not grant is refused with
charter: capability '<module>:<fn>' is not granted to this window. Without a charter the behaviour is unchanged (nothing is filtered). denyalways wins overallow, which lets you grant a whole module and carve out single functions ({ allow: ['fs:*'], deny: ['fs:writeText'] }).- Entry grammar:
module,module:fn,module:*or*. - Runtime revocation: capabilities can be dropped while the app runs; a destroyed window forgets its charter so ids cannot leak grants.
- Audit trail: every refusal broadcasts
charter.denied({ windowId, capability, module, fn }) to the main process.onCharterDenied()in the SDK subscribes to it. - Presets (
charterPresets.shell,.readOnlyFs,.node) for the common capability sets.
Where it is enforced
Only the renderer paths are filtered — the main process (control socket) is
trusted and never filtered, so module.invoke from @owear/core keeps working:
src/Window/Common/Messages.cpp— the WebView message path (ow.invoke).src/Webview/linux/Backend/Protocols.cpp—ow-sync://(blocking invoke).src/Webview/linux/Backend/Rpc.cpp—ow-rpc://(fetchinvoke).src/Webview/win/Backend/Handlers.cpp— the same path on WebView2.
Tighter default for the injected bridge
- The privileged surface is top-frame only. The bridge script is injected
with
WEBKIT_USER_CONTENT_INJECT_TOP_FRAME(Linux) and self-disables withwindow.top !== window.self(all platforms), so subframes — extension panels, embedded pages, remote iframes — no longer getwindow.ow. Note that the underlying message handler is registered per view: a subframe can still post raw messages into the kernel, so the charter is the real boundary (seetests/e2e/charter_guard.py). - The transport is captured at document-start.
window.webkit/window.chrome.webviewandJSON.stringifyare read once, in a closure, instead of being looked up on every call. Page scripts can no longer hook the channel and read or rewrite what the app sends. - Embedded webviews stay unprivileged by construction: the bridge is only
injected into the window's main view, never into the child webviews created
by the
webviewmodule.
Benchmarks
benchmarks/charter/ measures what the policy costs on the same machine as the
main suite (benchmarks/charter/RESULTS.md):
| state | ops/s | µs/call | overhead |
|---|---|---|---|
| off (no charter) | 2260 | 442.5 | — |
| charter: allow | 2226 | 449.2 | +1.5% |
| charter: deny | 2044 | 489.2 | +10.5% |
A granted call costs a lookup (+1.5%, inside noise); a refused one pays for the audit event it emits (+10.5%).
Tests
tests/e2e/charter_guard.py— 12 assertions against a live kernel: default behaviour, charter installation, refusal message, granted calls, the audit event,denywinning overallow, andclearCharter.packages/core/test/charter.test.mjs— the SDK side (defineCharternormalization, presets, event subscription).