Repository navigation
Security
-
Per-port session cookie: the session cookie is now
bare_session_<port>
(wasbare_session). Cookies are scoped to the host, not the port, so two
loopback servers sharing one cookie store overwrote each other's session. -
No session token in
handoff.json: the file now holds
{ origin, port, bootstrap }. A host that readtokenfrom it gets it from
runtime.start()inside the worklet instead. -
Gated directory mounts are sandboxed: files served from a
mountDir
without{ public: true }now carry the samesandboxContent-Security-Policy
as file mounts. An HTML or SVG file in a mounted folder (for example one a
peer synced) could previously run script in the app origin and drive every
plugin. Breaking for a consumer that served app code from a gated mount:
move it to the public mount. -
No unauthenticated non-loopback bind:
createLoopbackServer(and so
createWorkletRuntime, CLIhost=) throws whenhostis not a loopback
address and auth is off. The Host/Origin rule only stops browsers, so
mode=dev host=0.0.0.0exposed every plugin to the network. -
Host calls use their own request ids: the host IPC bridge no longer
reuses the page-suppliedrequestIdon the host channel, so a page cannot
supersede or cross-wire other pending host calls. -
vendor.mediavalidateskind: values other thanimage/videoare
refused before reaching the host or the mount path. -
Bounded log ingest:
POST /logstruncates messages to 8 KiB and tags to
64 chars, andGET /logs(text) escapes line breaks so a page entry cannot
pose as a backend line. -
Android navigation confinement (
BareWebViewClient): new
allowedOrigin; main-frame navigations off the loopback origin are cancelled
and http(s) links open in the system browser, matching iOS. The reference app
sets it and disables WebView file and content access. -
Dev harness: the supervisor no longer sends
Access-Control-Allow-Origin: *
and refuses requests with anOriginor a foreignHost, so a website can no
longer read instance credentials from it. -
Supply chain: the release job installs dependencies with
--ignore-scripts(esbuild rebuilt explicitly) while it holds the npm OIDC
token, andfetch-prebuildsrefuses to unpack without a real lockfile hash. -
Security model:
docs/security.md(threat model and review checklist),
SECURITY.md, thesecurity-reviewagent skill, andsecurity.test.ts
guarding greppable invariants. -
Origin rule in every mode (
core/server/loopback-gate.ts): with auth
off (the dev backend), the loopback server no longer accepts anyHostor
pageOrigin. A website open in the same browser could previously open the
dev protocol socket (WebSocket upgrades bypass CORS) and invoke every plugin.
Cross-origin dev pages must be listed inallowedOrigins(CLIorigin=). -
Secure defaults (
@ekrooh/bare/runtime):createWorkletRuntime
defaults to device mode (auth on, ephemeral port) whether or notstorageis
set, andresolveCliConfigkeeps those defaults unlessmode=devis passed
(auth=offwithoutmode=devis ignored with a warning).
The desktop sidecar forces auth on. A broken host configuration no longer
falls back to an unauthenticated dev server. -
Nonce-only login:
POST /loginno longer accepts the raw session token,
and the web transport no longer readswindow.__ekrooh.token. -
Path containment: path segments are percent-decoded and rejected (400)
when they decode to.,.., a separator or NUL; a directory mount never
serves a file whose real path (symlinks resolved) is outside its root. -
No error detail in responses: a throwing route or handler answers a bare
500 Internal Server Error; the cause goes to the log. -
Tighter headers: the session cookie is
SameSite=Strict; HTML
connect-srcnames the one protocol socket instead of anyws:host, and
the policy addsobject-src 'none',base-uri 'none',form-action 'self'
andframe-ancestors 'none'. Mounted session files (media) are served with a
sandboxing CSP so a hostile file opened as a document cannot run script at
the app origin. Every file response sendsX-Content-Type-Options: nosniff. -
Protocol socket: an
erroron the WebSocket (a peer reset) is contained
even when no connection handler listens, instead of crashing the worklet;
inbound messages are capped at the protocol's 16 MiB frame size. -
Log ingest:
POST /logsrecords every entry assource: 'web'and
validateslevel/tag; a non-object body or a malformedGET /logsquery
is a 400, and only an oversized body is reported as 413. -
Mount paths are normalized like request paths (
/media/a%20b/and
/media/a bname one mount); a mount path no request could match throws.
Changed
- BREAKING: dev mode is opt-in (
deviceMode: falseormode=dev); see
ADR 0006. Dev runners pass
mode=devand the Vite dev origin; the example Vite servers are pinned to
port 5173. - BREAKING:
getIPC()returnsnullwhen there is no host (it returned
undefinedtyped as the channel). - BREAKING:
handoff.jsonno longer carriestoken, and the session
cookie is namedbare_session_<port>(see Security). - A protocol socket upgrade inside the replacement interval
(wsReplaceMinIntervalMs) is held until the interval passes instead of being
dropped, so a quick reconnect no longer costs the page one of its 5 retries.
A refused upgrade is answered with its HTTP status (401, 403, or 429 for a
held upgrade superseded by a newer one) instead of a bare connection close. - The loopback server is split into the loopback gate (admission), the
protocol socket slot (core/server/protocol-socket.ts) and the HTTP serving
that remains instatic-file-server.ts. The host IPC and the WebSocket share
one frame channel (core/messages/frame-channel.ts) for reassembly, routing
and malformed-frame policy. createWorkletRuntimebuilds its protocol, registry and router through
createBareRuntimeContext, which now allocates one text encoder/decoder per
context instead of one per frame.
Added
allowedOriginsonLoopbackServerOptionsandWorkletRuntimeOptions, and
themode=and repeatableorigin=CLI tokens.
Removed
- The unused mantaq messenger prototype (
rpc-messenger.mantaq.ts,
pending-call.ts) and the@mantaq/sugardependency.