Some antivirus engines heuristically flag the self-signed, low-prevalence
Windows installer (issue #198). Give downloaders a way to confirm assets
are exactly what CI built: a checksum manifest plus signed SLSA provenance
verifiable via gh attestation verify, with triage + code-signing policy
docs linked from the README and release notes.
MV3 extension that captures the rendered active tab (via the shared
snapshot-extractor contract) and imports it into OpenPencil. Logic lives
in the new op-chrome-extension-core crate (wasm): endpoint rules,
chunked-transfer integrity, /mcp envelope + reply classification,
download-name sanitisation, SSO/account session parsing, and hub
snapshot-inbox delivery. JS is glue only (chrome.* APIs, fetch, popup
DOM, injected page functions).
Capture: full page + element pick. Delivery: local ingress
(POST /api/import/web-snapshot) with /mcp fallback, JSON download, and —
when signed in to OP Hub — the account snapshot inbox. Flat popup UI,
15-locale strings with an in-popup language switcher, store packaging.
Why a crate: keeps the security-sensitive logic in tested Rust rather
than glue JS, and the SW/popup split keeps dynamic import() out of the
service-worker graph (guarded by check-sw-imports).
Public relay was reachable only by setting OPENPENCIL_COLLAB_BOOTSTRAP_URL,
so a released build could never collaborate: no end user sets an environment
variable. Release builds now carry a hub URL per region, injected at compile
time from the release pipeline's secrets — the source tree keeps no
production endpoint, and an uninjected build stays environment-configured.
A persisted preference picks the region, exposed as a switch on the create
screen; the environment variables remain overrides that win and fail closed.
Each endpoint gets its own bootstrap cache record so switching regions cannot
overwrite the other hub's anti-rollback floor.
Also surfaces an unreachable device key as its own message instead of a
misleading reconnect notice, wraps notice text inside its bubble, and centres
button labels against their own height (28px buttons painted low).
The ~500-char opc1_ fragment is retired from every production surface; a
relay session now shares one 10-char Crockford code (1 region char + 9
random, 45 bits). The full invite is sealed with a blake3 encrypt-then-MAC
under keys derived from the code and stored on the locator control plane
under an independent code_id; guests claim from exactly the region the
code names, so neither the id nor the bearer ticket reaches uninvolved
regions. The store tombstones exhausted claim budgets (an id can never be
re-published under a burned code), caps codes per device key, and keeps
per-route ingress body limits. Connect failures now distinguish invalid,
expired, and relay-not-configured across all 15 locales, and the threat
model documents the operator-grindable 45-bit residual risk.
Addresses a security review of the collaboration subsystem. No auth
bypass, key leak, or document-plaintext exposure was found; every
finding below is availability or trust-boundary hardening.
Landed as one commit because the pieces are not separable: the
inbound-direction ceiling spans op-collab, op-collab-transport, and the
desktop host atomically, the guarded accept spans transport, smoke, and
the desktop host, and the boundary-gate rules only hold against the
final state. Splitting would produce commits that fail to build or fail
the gate.
Relay server (public, internet-facing):
- Charge pre-pairing capacity per source address. The auth-concurrency
semaphore was taken before the WebSocket upgrade and the peer address
was discarded, so one host could pin every permit by connecting and
going silent.
- Give renewals their own budget. Reauthentication competed for the same
semaphore, so an unauthenticated flood progressively closed live
tunnels with a policy error.
- Release the pair registration when the ready status fails to send; the
counterpart only reclaims it if it reads its pairing notice.
- Require the X25519 key file to be owned by the running user; mode bits
alone do not establish trust.
- Summarise capacity rejections instead of logging one line each.
Locator service:
- Rate-limit publishes per client instead of process-wide. One
unauthenticated caller could consume the whole budget and 429 every
tenant's invite issuance.
Collaboration protocol:
- Size the inbound envelope ceiling from the authenticated remote role
rather than sharing the 64 MiB snapshot ceiling in both directions, so
an admitted guest cannot force a 64 MiB JSON parse per frame. The
ceiling is applied before the discriminator and before the generic
value decode; a peer-declared snapshot kind cannot raise it.
- Reject display names carrying Unicode format characters, which render
identically to an existing participant's name.
- Reject avatar URLs pointing at non-globally-routable addresses.
Transport:
- Reclaim a pending-handshake seat from a peer that has not produced a
valid first handshake message, and raise the global ceiling. Sixteen
seats held for the full handshake window let four addresses deny every
join.
- Put inbound reassembly under an aggregate budget; only the outbound
aggregate was bounded.
- Stop heartbeats from refreshing the idle deadline in receive_transfer.
- Filter IPv4 link-local discovery advertisements, matching IPv6.
Relay client and trust roots:
- Bound server-initiated reauthentication per connection by count and
minimum interval, sized from the protocol's own cadence.
- Close the policy-file TOCTOU window by identity-checking the opened
file, and reject group/world-writable or foreign-owned policy files.
- Stop discarding bootstrap cache-write failures, which silently
disabled the anti-rollback generation floor.
Opening the verification page from the async poll callback was popup-
blocked (the 'click sign-in twice' bug); a placeholder window opened
synchronously inside the press is navigated once the URL arrives, and
closed on cancel/failure. The wasm login modal now says 'popup window'
instead of 'your browser' — the web editor already runs in one.
GitHub releases were named by bare tag because the action got no name.
The note's leading heading is the natural title, so publish it as the
release name and drop it from the body instead of repeating it.
Some Windows 11 machines flash-exit on launch: their WGL OpenGL is the
GDI-generic 1.1 software renderer (no/old GPU driver, VMs, RDP), which
Skia's GL backend rejects. The old WglThenEgl preference never fell
through to EGL because WGL *did* create a (1.1) context; the failure
only surfaced downstream in make_gl, after which new_desktop merely
eprintln'd (invisible under windows_subsystem="windows") and exited —
a silent crash. Not all machines: ones with a real GPU driver get
GL 3.0+ and work, which is why only some users saw it.
- native: new_desktop retries once via forced EGL (ANGLE) when the
native context can't drive Skia; machines that already work keep the
WGL path byte-for-byte unchanged.
- desktop: pop a native error dialog on GL-init failure instead of a
silent exit, so a GPU/driver problem is visible not mysterious.
- packaging/CI: ship arch-matched ANGLE runtime DLLs (libEGL /
libGLESv2 / d3dcompiler_47) next to the exe, staged from an Electron
release zip, so glutin's EGL fallback can load them.
The released extension was a shell without its runtime. The build matrix
now also produces op-host-web-server per target; the vsix release job
packages six platform vsix (vsce --target) embedding bin/<daemon> plus
web/{pkg,canvaskit}; the extension prefers the bundled runtime at spawn
(binary probe with exec-bit restore, env pointing the daemon at the
bundled web assets) with the workspace debug build as the dev fallback.
Three independent CI failures, none caused by the font-lock push:
1. CanvasKit web-host test variables_panel_open_does_not_paint_legacy_modal
false-tripped: its '> half viewport' heuristic was a proxy for 'no
full-viewport modal', but only passed because the property panel used
to narrow the canvas and clamp the floating VariablesPanel below
half-width. The nothing-selected-by-default change collapsed the
property panel, widening the canvas so the legitimate 820x480 panel
paints unclamped. There is no legacy modal. Retarget the assertion at
a near full-viewport round fill and positively assert the panel paints.
2. windows-aarch64 cross-check failed: op-host-services + op-orchestrator
pulled op-mcp's native-only 'script' feature unconditionally, dragging
in rquickjs-sys which ships no bindings for aarch64-pc-windows-msvc.
Enable 'script' per-target (off on that unshipped check-only arch).
3. Windows workspace tests segfaulted single-threaded (before the first
test prints, under --test-threads=1) — an intra-test race a thread
flag can't serialize. Switch the Windows runner to cargo-nextest (one
process per test) so the crash is isolated + named; doctests run
separately.
The STATUS_ACCESS_VIOLATION is not confined to op-host-native/-desktop: any
crate whose tests reach a skia FontMgr (DirectWrite) via NativeBackend or
SkiaMeasure crashes when that test binary runs across parallel worker threads
(op-host-services surfaced next). Excluding crates one-by-one doesn't
converge. Instead run the entire workspace with --test-threads=1 on Windows
only; each binary is serialized (cross-process concurrency is safe), covering
every skia-touching crate at once. macOS / Linux stay parallel.
The Windows STATUS_ACCESS_VIOLATION surfaced in the op_host_native test
binary once op-host-desktop was serialized: op-host-native's backend::skia
tests create a skia FontMgr (DirectWrite) and segfault when run across
parallel test-worker threads. Extend the single-threaded run to cover
op-host-native as well as op-host-desktop in both CI workflows.
Windows runs multi-line run: blocks under PowerShell, which reports only the
last command's exit code. Keeping the workspace test and the serialized
op-host-desktop test in one run: block would let a workspace-test failure be
masked by the second command passing. Split into two steps so each fails the
job independently.
Mirror the rust-check.yml fix into the cross-platform host-test step: the
op-host-desktop test binary segfaults on the Windows runner when its native
(skia/DirectWrite) tests run across parallel test-worker threads. Run that
crate single-threaded here too.
op-host-desktop tests build native NativeBackends (skia/DirectWrite fonts);
across cargo's parallel test-worker threads this segfaults on the Windows
runner. Split them out of the parallel workspace run and serialize with
--test-threads=1.
PRs touching vendor/casement (the winit fork) or vendor/agent rode
green with zero CI: rust-check's pull_request paths had no vendor
entry and rust-multiplatform only listed vendor/jian. Add them, and
refresh the Linux test-step comment now that the GPU smoke tests run
un-ignored via the provider GL loader.
47 commits from the align branch merged onto the force-updated remote
base (which had replayed an earlier snapshot of the same work plus new
overlay/pointer features and CI fixes). Conflict resolution: kept the
newer align side for the generation pipeline (orchestrator, mcp, skills,
design tools), kept the base side for the chat-panel test semantics and
graceful overlay teardown, fused both in sub_agent_session (design-turn
thinking policy + graceful epoch finish), and dropped the files each
side had deleted (legacy concurrent/dashboard paths, retired TS skills).
Deduped two identical replayed hunks (export.rs, chat_session_tests.rs).
Known issue carried over: provider_probe_host::landed_connected_outcome_
without_models_is_failure fails on a host with a live provider config
(env-sensitive test, both sides byte-identical there; green on CI).
Built-in design generation now runs as an agentic MCP tool-loop (reusing the
agent-rs BuiltInProvider), gated behind OPENPENCIL_DESIGN_AGENT_LOOP / the
Settings experimental toggle; the orchestrator stays the default.
- design-agent system prompt + in-process design toolset (parity-locked with
the MCP surface) + flag-gated Intent::Design routing
- spawn_agents execution as sequential sub-loops + live creation-mode badges
(per-agent glow + 'N/M designing...' header)
- new MCP tools: get_guidelines, ToolSearch, get_screenshot, get_editor_state,
export_nodes, spawn_agents; style-guide local audit
- #27 AI panel restyle: rounded tool cards + green check-rings, gray user
bubbles, model-pill bottom toolbar, header, empty-state pills, the
PARALLEL AGENTS (agent_team_size) 1x-6x chip dropdown
- multi-chat tabs: ChatSessions model (Deref-to-active) + tab row UI
(switch / close / + / Cmd+T) with each run bound to its tab
Large checkpoint commit spanning the working tree (Rust shell crates).