The Task 7 token audit missed a daemon-bound network call:
`agent_settings_mcp_server.rs::request_mcp_server_update` POSTs
`/api/mcp/server` via `window.fetch` (Reflect::get) and attached no
`X-OpenPencil-Token`. In managed mode (VS Code webview) the daemon's
auth gate rejects the request (fails closed), silently breaking the
MCP server start/stop toggle in the settings modal.
Fix: build the request URL from `daemon_base()` (absolute, matching
every other daemon call site in the crate) instead of a bare relative
path, and attach the token via a new shared `live_sync::daemon_token_for`
helper — factored out of `attach_daemon_headers` so XHR and non-XHR
call sites can't drift on the leak-guard policy (token only when set
AND the URL targets the daemon).
Corrected audit (grep -rn "XmlHttpRequest|fetch|Request::new"
crates/op-host-web/src, network-issuing sites only): 5 sites total —
the 4 live_sync XHR helpers + web_model_catalog + web_ai_transport +
iconify_web's fetch_text all already tokened via attach_daemon_headers
(iconify_web additionally verified to withhold the token from the
public Iconify CDN target); agent_settings_mcp_server's window.fetch
was the sole untokened site and is now fixed. No other window.fetch /
Request::new call sites exist in the crate.
Verification: cargo check --target wasm32-unknown-unknown -p
op-host-web --no-default-features --features canvaskit (pass, 1
pre-existing unrelated warning) / --features web (pass, clean);
cargo clippy -p op-host-web --all-targets -- -D warnings (pass, clean).
Add the webview-facing postMessage bridge (vscode_bridge.rs): token
bootstrap via Init, OpenDocument probe-conditional push, uncapped
Snapshot flush, SaveCommitted, and UseLocal/AcceptRemote conflict
resolution. All three edge/state events (dirty-changed, sync-conflict,
opened) are emitted only from the tick observer draining SyncGate's
consumable latches; handlers only mutate the gate. Reorder mount_ck so
the bridge listener installs first, an iframe awaits Init (2s fallback),
and the 400ms pull tick starts only after the daemon sync-reset (managed:
token; direct: legacy reset moved out of index.html) completes.
Token audit (X-OpenPencil-Token attached only when url starts with
daemon_base(); public requests never carry it):
live_sync.rs get -> daemon -> attach_daemon_headers
live_sync.rs get_with_status -> daemon -> attach_daemon_headers
live_sync.rs post_json -> daemon -> attach_daemon_headers
live_sync.rs post_json_with_status -> daemon -> attach_daemon_headers
web_model_catalog.rs:21 (/api/ai/models) -> daemon -> attach_daemon_headers
web_ai_transport.rs:93 (/api/ai/stream) -> daemon -> attach_daemon_headers
iconify_web.rs:311 fetch_text (daemon brand-catalog + public Iconify CDN)
-> attach_daemon_headers with url-prefix guard: token only on the
daemon URL, never on the public api.iconify.design requests
All other daemon-talking modules route through the live_sync helpers and
inherit the token. No window.fetch / Request::new sites in the crate.