v2.13.0 — tab-ownership hardening (three-pass audit)
A full-codebase security/correctness pass closing the tab-ownership enforcement gaps end to end (third audit pass, 2026-06-11).
Tab safety
- ~20 page-mutating tools called the engine directly with no ownership guard (cookies, all storage writers,
import_storage,drag,upload_file,paste_image,select_option,react_select_set,mock_route,throttle_network,override_geolocation,handle_dialog,resize) — onlyclick/fillwere guarded. All now call_assertTabOwnership()first. safari_run_scriptenforces ownership per step while the batch runs. The single pre-flight check was defeatable:evaluatewas exempt, and a mid-batchswitchTab/navigatecould move to the user's tab before aclickran. A refused step now aborts the whole batch._isURLOwnedrequired no path-segment boundary — owning/orgalso "owned"/org-evilon the same origin. Matching now requires a real/boundary; semantics extracted toownership-match.jsand locked by a unit suite (incl. the/orgvs/org-evilcase).- Ownership TTL enforced on use (was rewriting every entry's timestamp to
nowon each save → 30-min expiry never fired, leaking ownership across sessions). - Extension: owned-tab state survives MV3 service-worker restarts (
storage.session) — a restart used to wipe the in-memory map and silently re-enable the permissive "no tabs owned yet" path. Also fixesnavigate_and_readcache staleness, cross-window retargeting, and aclose_tablast-tab TOCTOU.
Robustness
- Swift daemon: GCD thread-leak respawn after 8 stuck executions,
autoreleasepoolper command, cleanexit(0)on stdin EOF, safe mouse-restore (no jump to 0,0). - Atomic
owned-tabs.jsonwrites (tmp + rename); bounded in-page console/network buffers; HTTP-bridge oversized-body parse fix;connect()re-entrancy lock; unguessableevaluateresult key (crypto.randomUUID).
Correctness
safari_press_keysends W3Ccodevalues (Enter,ArrowUp,Digit1…) — fixes apps routing onevent.code(Notion, Monaco, Google Docs).- Lexical fill dual-layer (JSON-then-JS) encoding;
set_cookie/mock_routeescaping viaescJsSingleQuote();/var/foldersallowlist fix.
Tooling
npm testnow runs thetest/unit suites (escaping + ownership) alongside the smoke test;engines.node >= 20.
Full detail in CHANGELOG.md.