Skip to content

Latest commit

 

History

History
1393 lines (1207 loc) · 162 KB

File metadata and controls

1393 lines (1207 loc) · 162 KB

ScriptVault Roadmap

Single source of truth for planned work. Tiered by execution priority. Completion history lives in git history and CHANGELOG.md; consolidated research conclusions live in RESEARCH.md. Legacy planning passes are reference-only under docs/archive/.

Roadmap version: Round 99 - Greasy Fork handoff cleanup hardening 2026-06-11. Shipped baseline: v3.11.0 (2026-05-19, tag pushed). main has additional unreleased hardening, TS promotion, Firefox validation, and release-trust commits through 2026-06-11. Test suite: 1582 Vitest cases green; npm audit --audit-level=high --omit=optional clean; 28/28 TS-promoted runtime entries; 0 mirrored; 0 divergent. Source floor: 400+ external URLs across Rounds 1-40. Every Now/Next item carries source IDs from the Appendix.

Last researched: Round 92 - 2026-06-07.


Market Context

Tampermonkey was briefly removed from CWS in July 2025 [S01] but returned and now sits at v5.5.0 with 12M users — the dominant incumbent. Violentmonkey remains MV2-only (v2.41.0, actively developed) but permanently blocked on Chrome 133+ (MV2 fully removed in Chrome 139) [S02, S03]. ScriptCat (4.5K stars) is MV3-native but niche [S04]. New entrants include Tweeks (YC W25, AI-native) and lightweight MV3 managers (OrangeMonkey, Vanilla Pudding, BareScript). Greasemonkey and FireMonkey remain Firefox-only MV2 [S05, S06].

ScriptVault is the only MV3-native, open-source, full-featured userscript manager with zero telemetry on the Chrome Web Store. Tampermonkey's return narrows the MV3 exclusivity window, but its closed-source and freemium model remain trust liabilities. The roadmap prioritises differentiation: ship Firefox AMO, land Edge, close GM API parity gaps, and capitalize on trust advantages (Ed25519 signing, AST analysis, update review, MIT license).

Correction — 2026-08-06. The paragraph above is out of date and the exclusivity claim no longer holds. Violentmonkey shipped MV3 stable in v2.43.0 on 2026-07-14 (first MV3 beta v2.41.2, 2026-07-07) and is at v2.47.0 as of 2026-08-06, with S3 sync, an opt-in "Alternative page mode" for true document-start timing, and Firefox CSP bypass. ScriptCat is at v1.4.0 stable with Firefox MV3 support, and FireMonkey revived with v3.0–v3.6 (2026-07). The durable differentiators are now Ed25519 signing (answers the still-open Violentmonkey #1558), the shipped update-review triad (answers the still-open #1023 +37 and #500 +30), AST risk analysis, and the accessibility gate — not MV3 itself. See RESEARCH.md (2026-08-06) and the P1 item "Refresh the README comparison table" below.


How to Read This Roadmap

Tier Meaning Timeframe
Now Active queue. Ship in v3.12.0. Days to weeks
Next Planned for v3.13.0-v3.14.0 after Now clears. Weeks to months
Later Validated direction, not yet scheduled. Months to quarters
Under Consideration Promising but needs more research or user signal. Unscheduled
Rejected Evaluated and declined, with reasoning. N/A

Priority labels within tiers: P0 safety/security/data-loss, P1 core workflow gaps, P2 polish, P3 nice-to-have.

Now (v3.12.0)

(All Now-tier items are credential/compliance blocked — see Roadmap_Blocked.md.)

Next

Deep Audit Backlog (2026-07-15)

Findings from a six-agent deep audit (workbench redesign UI, typed action dispatch, recent security commits, UserCSS/language service, locale/controllers, secondary surfaces). ~30 confirmed items were fixed across commits c62c19e (UserCSS engine), c02e26d (source maps), c13f7f8 (UserCSS UI + LSP), 780c837 (error-log URL retention), b419671 (workbench UI), e230d54 (secondary surfaces), and 259536b (controllers + a11y gate). The items below are lower-priority findings deferred for a later pass.

Deep Audit Backlog (2026-07-07)

Findings from a six-agent deep audit (correctness, security, cloud-sync/storage, background core, dashboard UX, dashboard modules, non-dashboard UI). The P0/P1 data-safety and background-hardening items were fixed in commits 17f45bf (sync data-safety + restrict-to-site scope expansion) and e233c94 (update lock + SRI @resource + scam-detector precision) — do NOT re-open those. The items below are the remaining unfixed findings.

Instructions for the AI working this backlog

  • Authoritative source is src/** (TypeScript). background.js, background.core.js, modules/*.js, and bg/*.js are GENERATED. The live service-worker logic lives in src/background/core.ts (a ~13k-line bridge); the focused modules under src/background/ (e.g. registration.ts, update-checker.ts, resource-loader.ts) are EXTRACTION TARGETS that are NOT wired into the runtime — editing them alone changes nothing at runtime. Grep the generated background.js for a symbol to confirm which copy is live before editing; usually you must fix BOTH core.ts (runtime) and the matching extraction module (drift-cleanliness). The @match ReDoS and restrict-to-site fixes both had to touch two copies for this reason.
  • After any src/** edit: npm run ts-runtime:generatenpm run build:bgnpm run typecheck → focused npx vitest run <files> → then npm run check before committing. Never edit generated artifacts by hand.
  • pages/dashboard.js is plain JS (no build step) and is LF-pinned. Edit with the Edit tool only — PowerShell Set-Content rewrites CRLF and breaks the \n-literal source-pin tests (support-snapshot-redaction). Many dashboard styles are duplicated between inline pages/dashboard.html <style> and pages/dashboard.css — check both.
  • Theme tokens: the 4 themes (dark/light/catppuccin/oled) are defined in pages/theme-tokens.css and the dashboard theme blocks. Prefer semantic tokens over raw hex; verify every change in all four modes.
  • Add a regression test for every fix (static source-pin at minimum; functional where a harness exists). Commit in logical batches with conventional messages; no AI authorship in commits.

Background core & security

Non-dashboard UI (popup / install / sidepanel / devtools)

Later

Under Consideration

  • UC-1. Safari via Native App Container [S40, S41] — Safari lacks userScripts API. Requires separate Swift project. Reconsider when user demand justifies it.
  • UC-2. Script Marketplace [S42] — ScriptVault is a manager, not a platform. GreasyFork integration serves the same need.
  • UC-3. AI-Assisted Script Editing [S43] — Must be opt-in, local-first. Reconsider when on-device LLMs are practical.
  • UC-4. Collaborative/Team Sharing — Small user base. Existing sync covers the 90% case.

Rejected

  • R-1. WXT Migration [S45] — Conflicts with concatenated service worker architecture. Revisit only if codebase moves to multi-file workers.
  • R-2. GM3 API Compatibility [S05] — Synchronous APIs conflict with MV3 isolation. TM/VM have moved on.
  • R-3. Safari via Polyfill [S40] — Safari lacks userScripts API. A polyfill cannot bridge this.
  • R-4. Chrome Sync Provider [S46] — 100KB quota unsuitable for scripts. Seven other providers exist.
  • R-5. Built-in Ad Blocking — uBlock Origin's domain. @webRequest DNR rules cover per-script blocking.

Round 16-34 Research Log

Cycle Angle Local evidence External signal Roadmap changes
45 Setup/onboarding reliability pages/dashboard.js, pages/popup.js, src/background/registration.ts, README.md support matrix Chrome 138+ per-extension Allow User Scripts; Firefox MV3 optional-only userScripts; user-script registrations are cleared on extension update [S48, S74] Added N-7 setup doctor and rehydration audit
46 Developer workflow Monaco editor, generated GM typings, install provenance, local-save receipts Tampermonkey external-editor FAQ, Tampermonkey Editors vscode.dev extension, current user complaints about copy-save-reload loops [S77, S78, S79] Promoted local file/watch work into X-8
47 Distribution and publishing Greasy Fork/OpenUserJS discovery and install provenance in dashboard/install UI Greasy Fork documents read-only API plus prefilled update form requiring user session cookie [S76] Added X-9 and corrected L-10 from direct API to publish handoff
48 Implementation decomposition pages/dashboard.js editor save/install paths, pages/dashboard.html editor toolbar, src/background/core.ts, docs/release-runbook.md, trust receipt tests Chrome File System Access user-gesture/support constraints, Greasy Fork prefill form rules, Chrome/Firefox userScripts update clearing, CWS remote-code policy [S47, S74, S75, S76, S81] Added N-8 and expanded X-8/X-9 into implementation slices
49 Compliant background execution architecture src/background/background-runner*.ts, src/background/background-wrapper.ts, offscreen.js, scripts/check-cws-remote-code.mjs, docs/background-scripts-design.md, docs/cws-remote-code-compliance.md Chrome offscreen limits, remote-hosted-code sandbox guidance, ScriptCat background/cron docs [S82, S83, S84] Expanded X-2 with sandbox-runner candidate, rejected alternatives, and prototype acceptance gates
50 SPA navigation support src/background/wrapper-builder.ts, src/background/registration.ts, content.js, tests/wrapper-dom-security.test.js, tests/match-top-39-11.test.js, README/dashboard examples Navigation API route events, same-origin scope, and extension webNavigation permission tradeoffs [S85, S86, S87, S88] Expanded X-3 from feature note into implementation-ready URL-change contract and verification gates
51 Local-save trust receipts pages/dashboard.js, src/background/core.ts, src/background/trust-receipt.ts, src/types/messages.ts, src/types/script.ts, src/modules/sync-easycloud.ts, tests/trust-receipt*.test.js, tests/install-source.test.js, tests/support-snapshot-redaction.test.js, tests/reregister-script.test.js File System Access user-gesture/handle-storage limits and CWS user-data/privacy disclosure expectations [S75, S81, S89, S90] Expanded N-8 with local-source override, autosave coalescing, export/sync redaction, and receipt-operation test gates
52 Developer workspace/local file binding pages/dashboard.js, pages/dashboard.html, pages/dashboard.css, src/types/script.ts, src/background/core.ts, src/background/import-export.ts, src/background/cloud-sync.ts, src/modules/sync-easycloud.ts, src/storage/script-db.ts, cloud-sync/export tests File System Access stored-handle, permission-persistence, user-gesture, secure-context, and CWS user-data disclosure constraints [S81, S89, S90, S91, S92] Expanded X-8 with local-only binding store, permission-state UI, refresh-only first release, and export/sync redaction gates
53 Setup rehydration evidence src/background/core.ts, background.core.js, background.js, src/types/messages.ts, tests/local-health-report.test.js, dashboard support snapshot export Chrome userScripts docs, Chrome 138+ Allow User Scripts, Firefox optional-only userScripts, and update-time registration clearing [S47, S48, S74] Implemented aggregate last-registration-sweep evidence in local health reports and support snapshots
54 Local-save receipt implementation pages/dashboard.js, src/background/core.ts, src/background/trust-receipt.ts, src/types/messages.ts, src/types/script.ts, tests/trust-receipt.test.js, tests/local-save-trust-receipt.test.js CWS user-data disclosure and File System Access user-gesture/stored-handle constraints [S81, S89, S91] Shipped explicit local editor receipt payloads and metadata-fallback suppression for manual saves/autosaves
55 Autosave receipt coalescing pages/dashboard.js, src/background/core.ts, src/types/messages.ts, tests/local-save-trust-receipt.test.js, tests/reregister-script.test.js CWS local-storage disclosure plus File System Access handle permission recheck and persistent-permission behavior [S81, S89, S91] Added ephemeral autosave coalescing keys, in-memory rollback-history reuse, and reregister/export-safety source guards
56 Local workspace binding store src/storage/idb.ts, src/storage/script-db.ts, src/modules/storage.ts, src/background/core.ts, modules/storage.js, tests/storage.test.js, export/sync/support tests File handles are serializable to IndexedDB; permission must be rechecked with queryPermission()/requestPermission(); local sensitive data remains disclosure-relevant [S81, S89, S91] Added local-only binding storage plus JSON export, CloudSync, EasyCloud, and support-snapshot redaction fixtures
57 Dashboard local file binding pages/dashboard.html, pages/dashboard.js, tests/local-workspace-dashboard.test.js File picker support must be feature-detected and invoked from a user gesture; stored handles require permission-state rechecks; local sensitive data still needs clear disclosure [S81, S89, S91] Added the feature-detected Bind File control, local IndexedDB handle persistence, permission summary chip, and no-save/no-code binding tests
58 Local file refresh review pages/dashboard.html, pages/dashboard.js, tests/local-workspace-dashboard.test.js Stored handles may need requestPermission() from a user gesture before reads; local files should be reviewed before applying executable code [S81, S89, S91] Added Refresh File and Unbind, review-diff apply, permission reconnect/error summaries, no-change handling, and local-file receipt tests
59 Deep audit security src/background/wrapper-builder.ts, src/background/core.ts, generated runtime artifacts, tests/wrapper-dom-security.test.js, docs/research-deep-audit-2026-06-06.md Local audit found srcdoc bypass in GM_addElement; iframe srcdoc is raw HTML rather than a normal URL attribute Blocked srcdoc for direct attrs and sanitized innerHTML, regenerated runtime artifacts, and pinned the bypass regression
60 Crontab execution isolation src/background/core.ts, generated runtime artifacts, tests/crontab-next-fire.test.js, docs/research-deep-audit-2026-06-06.md Local audit found scheduled scripts running through chrome.scripting.executeScript in ISOLATED world, which can expose extension APIs to userscript code Moved scheduled execution to chrome.userScripts.execute in USER_SCRIPT world with a MAIN-world fallback only, removed the scheduled new Function path, and pinned the isolation regression
61 PublicAPI internal-host parity src/modules/public-api.ts, modules/public-api.js, background.js, tests/public-api.test.js, tests/source-hardening-parity.test.js Local audit found PublicAPI's private internal-host copy missing .localhost, TEST-NET, benchmarking, Class E, and IPv4-mapped IPv6 hex cases already enforced by InternalHostGuard Reused the canonical isInternalHost guard for trusted origins, web install URLs, and webhook URLs, regenerated runtime artifacts, and pinned behavior/source parity regressions
62 S3 settings validation src/config/settings-schema.json, pages/dashboard.html, pages/dashboard.js, scripts/check-settings-schema.mjs, tests/dashboard-a11y.test.js WCAG 2.1 SC 3.3.1 error identification, MDN constraint validation, and aria-invalid guidance still favor field-specific text errors and custom validity [S07, S08] Added S3 endpoint/region/bucket/object-key validation metadata, native hints, accessible error nodes, blur hooks, and focused a11y/schema coverage
63 Sync credential validation src/config/settings-schema.json, pages/dashboard.html, pages/dashboard.js, scripts/check-settings-schema.mjs, tests/dashboard-a11y.test.js WCAG 2.1 SC 3.3.1, MDN constraint validation, and MDN aria-invalid guidance still support custom validity plus field-specific error messages [S07, S08] Added WebDAV, sync passphrase, and S3 credential validation metadata, error nodes, native length limits, blur hooks, and an encryption toggle guard
64 Editor select validation src/config/settings-schema.json, pages/dashboard.html, pages/dashboard.js, scripts/check-settings-schema.mjs, tests/dashboard-a11y.test.js MDN documents HTMLSelectElement.setCustomValidity() and option values as the select contract; WCAG error identification still requires text errors for invalid choices [S07, S08] Added validation metadata, error nodes, and save-blocking allowed-option checks for editor font size, indentation width, and tab size
65 Interval select validation pages/dashboard.html, pages/dashboard.js, src/config/settings-schema.json, scripts/check-settings-schema.mjs, tests/dashboard-a11y.test.js MDN select option values plus HTMLSelectElement.setCustomValidity() support the same allowed-option validation contract [S07, S08] Added validation metadata/error nodes for update check, notification delay, and externals intervals; preserved 0/"Never" with nullish fallbacks and validator conversion
66 Security select validation pages/dashboard.html, pages/dashboard.js, src/config/settings-schema.json, scripts/check-settings-schema.mjs, tests/dashboard-a11y.test.js MDN select option values plus HTMLSelectElement.setCustomValidity() support the same allowed-option validation contract, and WCAG error identification expects text feedback for invalid choices [S07, S08] Added validation metadata, error nodes, and save-blocking allowed-option checks for content script API, sandbox mode, CSP modification mode, and HTTP header modification mode
67 Action behavior select validation pages/dashboard.html, pages/dashboard.js, src/config/settings-schema.json, scripts/check-settings-schema.mjs, tests/dashboard-a11y.test.js MDN select option values plus HTMLSelectElement.setCustomValidity() support the same allowed-option validation contract, and WCAG error identification expects text feedback for invalid choices [S07, S08] Added validation metadata, error nodes, and save-blocking allowed-option checks for default tab type, local file, cookie, communication, SRI, include, @connect, incognito, page filter, block severity, strict mode, and top-level await selects
68 Remaining select validation pages/dashboard.html, pages/dashboard.js, src/config/settings-schema.json, scripts/check-settings-schema.mjs, tests/dashboard-a11y.test.js MDN select option values plus HTMLSelectElement.setCustomValidity() support the same allowed-option validation contract, and WCAG error identification expects text feedback for invalid choices [S07, S08] Added validation metadata, error nodes, and save-blocking allowed-option checks for all remaining schema-backed selects; popup columns now validates before numeric conversion
69 Custom CSS validation pages/dashboard.html, pages/dashboard.js, src/config/settings-schema.json, scripts/check-settings-schema.mjs, tests/dashboard-a11y.test.js WCAG error identification and MDN constraint validation support text errors, native length constraints, and custom validity for malformed free-form text [S07, S08] Added validation metadata, maxlength, accessible error text, and save-blocking validation for custom CSS; whitespace is preserved while unsafe control characters and overlarge CSS are rejected
70 Settings validation acceptance gate scripts/check-settings-schema.mjs, tests/settings-schema.test.js, src/config/settings-schema.json, pages/dashboard.html, pages/dashboard.js WCAG error identification and MDN constraint validation still require user-facing errors for malformed input controls [S07, S08] Added the durable dashboard-backed validation-metadata requirement and closed N-1 after the repository schema passed it
71 Guarded GM.fetch src/background/core.ts, src/background/wrapper-builder.ts, background.core.js, background.js, scripts/generate-gm-types.mjs, lib/scriptvault.d.ts, tests/gm-namespace-parity.test.js, tests/gm-types.test.js, docs/gm-namespace-parity.md Tampermonkey/Violentmonkey GM API parity still depends on promise-style network helpers, but ScriptVault must keep network access behind GM_xmlhttpRequest policy [S10, S11, S12] Added GM.fetch/GM_fetch over the existing XHR bridge, pinned that no background GM_fetch action exists, and closed N-2
72 SPA URL-change proof src/background/core.ts, src/background/wrapper-builder.ts, generated runtime artifacts, tests/urlchange-wrapper.test.js, README.md Navigation API route events plus history/popstate/hashchange fallbacks remain the least-permission page-level path for SPA userscript reruns [S85, S86, S87, S88] Added a shared URL-change scheduler, microtask/frame rechecks, duplicate suppression, jsdom coverage, and README author examples for window.onurlchange
73 Monaco package guard scripts/check-monaco-package-contract.mjs, tests/monaco-package-contract.test.js, package.json, docs/monaco-esm-migration-plan.md Monaco AMD remains deprecated, but the current v3.12 packaging contract must stay local and Firefox-safe until the ESM bundle is deliberately switched [S17, S24] Added a static gate for local AMD Chromium packaging, remote/CDN sandbox rejection, Firefox Monaco exclusion, npm-check wiring, and plan drift
74 Monaco ESM prototype src/editor/monaco-esm-entry.ts, esbuild.config.mjs, scripts/check-monaco-esm-prototype.mjs, tests/monaco-esm-build.test.js, docs/audit/monaco-esm-prototype-2026-06-06.json Monaco ESM needs a local bundled editor plus file-backed workers before the sandbox can leave AMD [S17, S24] Added an ignored lib/monaco-esm/ prototype build, deterministic worker outputs, font loader handling, post-build evidence checks, and size/layout evidence
75 Monaco ESM size budget scripts/check-monaco-esm-prototype.mjs, tests/monaco-esm-prototype-check.test.js, docs/audit/monaco-esm-prototype-2026-06-06.json, docs/monaco-esm-migration-plan.md The full JavaScript/TypeScript worker preserves userscript language features, but package growth must be explicit before switching from AMD [S17, S24] Selected the full-worker Chromium strategy, added total/compressed/per-file budgets, recorded gzip evidence, and pinned budget regressions
76 Monaco ESM sandbox switch pages/editor-sandbox.html, esbuild.config.mjs, scripts/check-monaco-package-contract.mjs, tests/monaco-esm-plan.test.js, docs/cws-remote-code-compliance.md The sandbox can leave deprecated AMD only if it imports packaged ESM assets and keeps fallback behavior [S17, S24] Switched the sandbox to local ESM CSS/import loading, removed AMD copy steps, rejected AMD loader regressions, and added sandbox script parsing coverage
77 Monaco ESM fallback harness tests/monaco-esm-sandbox-loader.test.js, pages/editor-sandbox.html Browser-profile proof can be blocked, but the loader/fallback contract still needs deterministic coverage [S17, S24] Added a VM/DOM harness that executes the sandbox script, validates ESM path requests, ready posting, and missing-bundle fallback routing
78 Monaco ESM Chromium sandbox smoke tests/e2e/monaco-esm-sandbox.spec.js, tests/e2e/helpers/extension-fixture.js The ESM sandbox switch must run in a real Chromium extension page, not only jsdom/VM harnesses [S17, S24] Added a Playwright extension-page smoke for the packaged sandbox ready path and routed missing-bundle fallback path
79 Monaco adapter dashboard smoke tests/e2e/monaco-adapter-dashboard.spec.js The ESM editor must persist real dashboard edits through the CodeMirror-compatible Monaco adapter [S17, S24] Added a Playwright dashboard smoke for edit-open, adapter readiness, toolbar save, reload, and adapter value persistence
80 browser namespace alias src/shared/utils.ts, shared/utils.js, scripts/generate-ts-runtime-modules.mjs, pages/dashboard-firefox-compat.js, focused tests Chrome 148 exposes browser, so Chrome/Firefox code should converge without exposing extension APIs to userscript/page worlds [S25] Added a generated extension-context alias, fixed Chromium-vs-Firefox detection when browser.runtime exists, and pinned wrapper boundary coverage
81 Trusted Types author docs README.md, pages/dashboard.html, tests/trusted-types-docs.test.js MAIN-world scripts on Trusted Types pages need explicit author guidance, while ScriptVault's default USER_SCRIPT path should stay documented as the safer default [S26] Added README and Help guidance plus a static test that pins the docs and confirms no runtime policy shim was introduced
82 Subscription refresh scheduling src/background/core.ts, settings defaults/schema/types, dashboard subscription controls, generated runtime artifacts, focused tests Chrome alarms remain the MV3-safe periodic work primitive, with same-name replacement and a 30-second minimum interval in current Chrome [S93] Added a managed subscription refresh alarm, visible auto-refresh/interval controls, feed health labels, scheduler resync on add/remove/settings changes, and static contract coverage
83 Local workspace refresh status pages/dashboard.js, src/storage/script-db.ts, modules/storage.js, focused dashboard/storage tests File System Access handles can persist in IndexedDB, but permission/read state must be visible because access can revert to prompt or fail between sessions [S81, S91, S92] Added explicit bound/unchanged/applied/cancelled/error refresh state summaries for the editor local-file chip without exposing handles or absolute paths
84 Local workspace health evidence src/background/core.ts, pages/dashboard.js, focused health/support tests Support snapshots need aggregate local-workspace evidence without file handles, paths, or local names [S81, S91, S92] Added local workspace health counts, refresh-age buckets, and support-snapshot sanitization unless script inventory is opted in
85 Local refresh acceptance hardening pages/dashboard.js, src/background/core.ts, focused local-workspace/local-health tests File System Access reads must reject oversized executable files before loading text, and parse/apply failures need distinct operator feedback [S81, S91, S92] Added the 5 MB bound-file read cap, too-large and parse-failed status paths, and regression coverage for normal local apply registration
86 Greasy Fork publish preflight pages/dashboard.html, pages/dashboard.js, tests/greasyfork-publish-handoff.test.js Greasy Fork documents read-only APIs plus multipart prefilled update forms, not a credentialed write API [S76] Added editor publish preflight, metadata validation, new/update target detection, exact code preview, copy/download fallback, and static guards against background publish endpoints
87 Greasy Fork publication receipt pages/dashboard.html, pages/dashboard.js, tests/greasyfork-publish-handoff.test.js Store-safe handoff needs a local receipt only after user-confirmed submission, without storing submitted code or account/session material [S76] Added post-handoff confirmation, local-only publication receipt storage, Info-panel reload display, receipt trimming, and Chromium dashboard smoke coverage
88 Greasy Fork receipt history pages/dashboard.js, tests/greasyfork-publish-handoff.test.js Greasy Fork still documents read-only APIs plus session-cookie prefilled form POSTs, so local fallback handling must not claim a credentialed publish API [S76] Added per-script publication receipt history display, local-only clear-history management, source/account redaction copy, focused static coverage, and Chromium extension smoke coverage
89 Greasy Fork receipt summary fallback pages/dashboard.js, tests/greasyfork-publish-handoff.test.js Clipboard writes are a user-initiated local action, while Greasy Fork publication still happens only through the user-reviewed prefilled form [S76, S94] Added copyable sanitized receipt summaries for local publication history and Chromium smoke coverage that proves copied text omits source/account/session data
90 Greasy Fork receipt export fallback pages/dashboard.js, tests/greasyfork-publish-handoff.test.js Local Blob URLs plus anchor download preserve a browser-native export path for sanitized local receipt text [S76, S95, S96] Added downloadable sanitized receipt summaries, safe receipt filenames, object URL revocation, focused static coverage, and Chromium smoke coverage for exported text redaction
91 Greasy Fork session-check polish pages/dashboard.js, tests/greasyfork-publish-handoff.test.js Greasy Fork publication still depends on the user's browser session, so the preflight modal should let users open Greasy Fork without posting script data [S76] Added a user-initiated Open Greasy Fork modal action that opens only the base URL with noopener/noreferrer, plus focused static coverage and Chromium smoke proof
92 Enterprise policy provisioning manifest.json, managed-storage-schema.json, src/background/core.ts, pages/dashboard.js, docs/enterprise-policy-provisioning.md, focused manifest/runtime tests Chrome managed storage requires a manifest-declared schema; managed storage is read-only policy data and can be restricted to trusted contexts [S28, S97, S98] Added schema wiring, trusted-context access narrowing, deterministic managed install tags, a dashboard Managed badge, and administrator docs
93 Enterprise policy diagnostics src/background/core.ts, src/types/messages.ts, local health/support snapshot tests Chrome policy deployment is normally inspected at chrome://policy, but ScriptVault support snapshots need only aggregate extension-side evidence [S97, S98, S99] Added managed policy support/read/configuration/install counts and warning signals to local health without exposing policy values
94 GM value-sync support snapshot allowlist pages/dashboard.js, tests/support-snapshot-redaction.test.js Chrome extension diagnostics should keep privileged userscript/storage state behind extension-controlled messaging and export only support-safe aggregates [S47, S98] Added an explicit dashboard-side allowlist for GM value-sync local-health export data, including clamped retry-ready last-result evidence and known warning IDs only
95 GM value-sync support summary polish pages/dashboard.js, tests/support-snapshot-redaction.test.js Support operators need pre-export visibility into aggregate local-health diagnostics while the privacy envelope keeps script/value data out of the UI and export path [S47, S98] Cached local health during utilities refresh/export and added aggregate GM value-sync retry readiness to the support snapshot summary
96 GM value-sync retry-age diagnostics src/background/core.ts, src/types/messages.ts, generated runtime artifacts, pages/dashboard.js, focused local-health/support tests Support diagnostics should distinguish fresh retry-ready write failures from stale ones while still exporting only aggregate local-health evidence [S47, S98] Added sanitized retry-age minutes/buckets to last-result health and support-summary output
97 GM value-sync bounded retry history src/background/core.ts, src/types/messages.ts, generated runtime artifacts, pages/dashboard.js, focused local-health/support tests Chrome storage quota guidance favors small JSON-serializable local state, and CWS user-data policy favors aggregate/anonymized operational diagnostics [S47, S98] Added a five-entry aggregate retry-history store, local-health summary counts, support-snapshot allowlisting, and clear-all cleanup
98 GM value-sync stale retry cleanup src/background/core.ts, src/types/messages.ts, generated runtime artifacts, pages/dashboard.js, focused local-health/support tests Chrome storage APIs support extension-local JSON state, and CWS user-data guidance favors minimal aggregate diagnostics for support exports [S89, S97] Added seven-day retry-history retention, stale-entry pruning on sync persistence, local-health retained/stale counts, and support-snapshot allowlisting
99 GM value-sync retry resolution drill tests/source-cloud-sync.test.js Empty-local-only retries should prove a transient write failure resolves on a later sync without widening stored or exported diagnostics [S89, S97] Extended the write-failure fixture through a second sync that applies the preserved remote bundle and keeps retry result output identifier/value-free
100 GM value-sync retry-resolution health summaries src/background/core.ts, src/types/messages.ts, generated runtime artifacts, pages/dashboard.js, focused local-health/support tests Support diagnostics need aggregate proof that retry-ready failures later cleared without exporting scripts, values, account data, or provider errors [S89, S97] Added local retry-resolution records, support-safe health/export summaries, age buckets, and clear-all cleanup for resolution evidence
101 GM value-sync retry-resolution stale cleanup src/background/core.ts, generated runtime artifacts, tests/local-health-report.test.js Local diagnostic records should keep the same retention boundary as support-safe history evidence [S89, S97] Removed stale or malformed retry-resolution records during sync result persistence when no fresh resolution is written
102 GM value-sync resolution-history support evidence src/background/core.ts, src/types/messages.ts, generated runtime artifacts, pages/dashboard.js, focused local-health/support tests Support diagnostics need bounded recent recovery evidence without exporting script/value/provider details [S89, S97] Added a five-entry aggregate retry-resolution history, local-health/support summaries, stale-count reporting, and clear-all cleanup
103 GM value-sync retry-resolution export hardening pages/dashboard.js, tests/support-snapshot-redaction.test.js Support exports should not overstate retry-resolution evidence from malformed local-health input [S47, S98] Rejected malformed resolution records without prior retry-ready evidence, zeroed retained-history totals when entries sanitize to zero, and normalized history timestamp ranges
104 GM value-sync retry-resolution source invariants tests/local-health-report.test.js Retry-resolution evidence must remain tied to successful clean retries after prior retry-ready history [S89, S97] Added source-contract coverage for resolution record gates and persistence-time stale pruning of retry-resolution history
105 GM value-sync retry-resolution support summary polish pages/dashboard.js, tests/support-snapshot-redaction.test.js Operators should see aggregate stale/excluded recovery evidence before exporting support snapshots [S47, S98] Added support-card labels for historical retry-resolution applies and stale retry-resolution-history events excluded
106 GM value-sync support-summary clamp hardening pages/dashboard.js, tests/support-snapshot-redaction.test.js Pre-export support summaries should format only non-negative integer aggregate counts [S47, S98] Re-clamped all displayed GM value-sync support-summary counts before formatting
107 GM value-sync retry-resolution stale-history evidence tests/local-health-report.test.js Stale retry-resolution history should stay visible only as aggregate exclusion evidence [S89, S97] Added local-health source coverage for include-stale reads, stale exclusion counts, retained-entry filtering, typed output, and privacy flags
108 GM value-sync support-summary schema drift coverage tests/support-snapshot-redaction.test.js Pre-export summaries should read only reviewed sanitized fields, not raw local-health input [S47, S98] Pinned the exact sanitized GM value fields read by the support summary and rejected raw local-health field access
109 GM value-sync support export schema drift coverage tests/support-snapshot-redaction.test.js Support exports should expose only reviewed aggregate diagnostic fields at every nested level [S47, S98] Pinned the exact returned sanitizer keys for GM value sync, last-result, retry-resolution, retry-resolution-history, and retry-history exports
110 GM value-sync support privacy schema drift coverage tests/support-snapshot-redaction.test.js Support export privacy metadata should stay explicit and false for sensitive data classes [S47, S98] Pinned the exact nested privacy keys and false values for GM value sync support exports
111 GM value-sync support warning-count schema drift coverage tests/support-snapshot-redaction.test.js Support exports should allow only reviewed GM value warning identifiers [S47, S98] Pinned the exact warning-count allowlist and rejected raw warning-count key iteration
112 GM value-sync retry-age bucket schema drift coverage tests/support-snapshot-redaction.test.js Retry-ready and retry-resolution support exports should share reviewed age buckets [S47, S98] Pinned the exact retry-age bucket allowlist and unknown fallback for support exports
113 GM value-sync retry-resolution cleanup guard tests/local-health-report.test.js Stale or malformed local retry-resolution records should be removed without writing null diagnostics [S89, S97] Pinned the single-record cleanup helper and persistence-time remove path
114 GM value-sync retry-resolution history storage contract tests/local-health-report.test.js Local retry-resolution history should retain only aggregate recovery evidence [S89, S97] Pinned the exact stored retry-resolution history entry keys and rejected privacy/raw identifier fields
115 GM value-sync support summary phrase drift coverage tests/support-snapshot-redaction.test.js Pre-export summaries should use only reviewed aggregate diagnostic wording [S47, S98] Pinned fallback, opt-in, retry, history, stale, and capped-value summary phrases while rejecting raw identifier labels
116 GM value-sync support summary count-order coverage tests/support-snapshot-redaction.test.js Pre-export summaries should present aggregate diagnostics in reviewed order [S47, S98] Pinned baseline, retry, resolution, history, stale, warning, and final join order
117 GM value-sync support summary warning-total coverage tests/support-snapshot-redaction.test.js Warning totals should be computed only from sanitized support-export warning counts [S47, S98] Pinned sanitized warning-count reduction, shared count clamping, and capped/excluded aggregate wording
118 GM value-sync retry-resolution history type schema coverage tests/local-health-report.test.js Typed local-health responses should expose only reviewed aggregate retry-resolution history fields [S89, S97] Pinned retry-resolution-history response fields, privacy keys, and raw identifier exclusions
119 GM value-sync support summary fallback-state coverage tests/support-snapshot-redaction.test.js Pre-export summaries should return reviewed fallback states before formatting counts [S47, S98] Pinned sanitize-first unchecked/unavailable fallback order before aggregate count formatting
120 GM value-sync retry-resolution typed privacy coverage tests/local-health-report.test.js Typed local-health responses should expose only reviewed aggregate retry-resolution fields and privacy keys [S89, S97] Pinned single retry-resolution response fields, privacy keys, and raw identifier exclusions
121 GM value-sync retry-history typed privacy coverage tests/local-health-report.test.js Typed local-health responses should expose only reviewed aggregate retry-history fields and privacy keys [S89, S97] Pinned retry-history response fields, privacy keys, and raw identifier exclusions
122 GM value-sync typed privacy coverage tests/local-health-report.test.js Typed local-health responses should keep the top-level GM value sync diagnostic envelope reviewed [S89, S97] Pinned top-level GM value sync response fields, privacy keys, and raw identifier exclusions
123 GM value-sync last-result typed schema coverage tests/local-health-report.test.js Typed local-health responses should keep persisted sync result diagnostics aggregate-only [S89, S97] Pinned last-result response fields, retry-age fields, and raw identifier/privacy exclusions
124 GM value-sync support unavailable-state wording coverage tests/support-snapshot-redaction.test.js Unavailable pre-export summaries should stay generic and support-safe [S47, S98] Pinned generic unavailable wording and rejected provider/account/credential/script/key/error detail
125 GM value-sync last-result support export clamp coverage tests/support-snapshot-redaction.test.js Support exports should not overstate retry-ready last-result evidence [S47, S98] Pinned retry-ready clamping to sanitized failure/preserved counts and retry-age gating
126 GM value-sync support unchecked-state wording coverage tests/support-snapshot-redaction.test.js Unchecked pre-export summaries should stay generic and support-safe [S47, S98] Pinned generic unchecked wording and rejected provider/account/credential/script/key/error detail
127 GM value-sync last-result timestamp sanitizer coverage pages/dashboard.js, tests/support-snapshot-redaction.test.js Support exports should normalize last-result timestamps through the reviewed helper [S47, S98] Routed last-result timestamp export through sanitizeSupportSnapshotTimestamp() and pinned the shared-helper path
128 GM value-sync retry-age unknown bucket coverage src/background/core.ts, background.core.js, tests/local-health-report.test.js Retry-ready diagnostics with missing timestamps should not be labeled fresh [S47, S98] Classified null/undefined retry ages as unknown and pinned the local-health last-result gating path
129 GM value-sync support nested-field coverage tests/support-snapshot-redaction.test.js Pre-export support summaries should read only reviewed nested sanitized fields [S47, S98] Pinned nested last-result, retry-resolution, retry-resolution-history, and retry-history field allowlists
130 GM value-sync retry-history timestamp retention coverage tests/support-snapshot-redaction.test.js Support exports should not retain retry-history timestamps when retained history is empty [S47, S98] Pinned retained-history timestamp helper use for retry and retry-resolution histories
131 GM value-sync retry-resolution timestamp range coverage pages/dashboard.js, tests/support-snapshot-redaction.test.js Support exports should not expose impossible retry-resolution timestamp ranges [S47, S98] Clamped retry-resolution latest retry timestamp to the resolution timestamp before export
132 GM value-sync retry-resolution age-bucket gating pages/dashboard.js, tests/support-snapshot-redaction.test.js Support exports should not retain retry-resolution age buckets without age minutes [S47, S98] Gated retry-resolution age bucket export on retained age minutes and defaulted missing evidence to unknown

Competitive Position Summary

Capability ScriptVault Tampermonkey Violentmonkey ScriptCat
MV3 native Yes Yes No (dead on Chrome) Yes
Open source MIT No MIT GPL-3.0
Chrome Web Store Published Published (12M) Blocked (MV2) Published
GM API (35+) Yes Yes Yes Yes
Monaco editor Yes No Yes (MV2) No
DevTools panel Yes No No No
Side panel Yes No No No
Script signing (Ed25519) Yes No No No
AST analysis (31 detectors) Yes No No No
Cloud sync providers 7 6 4 Built-in
Update diff + review inbox Yes No No (most-requested) No
Version rollback Yes (3) No No No
Import quarantine Yes No No No
Sigstore provenance Yes No No No
Workspaces Yes No No No
Command palette Yes No No No
Performance budgets Yes No No No
Background/cron scripts Partial No No Yes
Enterprise provisioning Yes (Chrome/Edge) Yes (v5.5) No No
MCP/AI integration No Yes (v5.5) No No
Firefox published No (AMO-ready) Yes Yes Yes
Edge published No (pkg-ready) Yes No Yes

Appendix: Sources

ID Source URL
S01 TM CWS removal (#2498) Tampermonkey/tampermonkey#2498
S02 VM Chrome compat (#2340) violentmonkey/violentmonkey#2340
S03 VM still MV2 (#2284) violentmonkey/violentmonkey#2284
S04 ScriptCat MV3 https://github.com/scriptscat/scriptcat
S05 Greasemonkey https://github.com/greasemonkey/greasemonkey
S06 FireMonkey https://github.com/erosman/firemonkey
S07 WCAG 2.1 SC 3.3.1 https://www.w3.org/WAI/WCAG21/Understanding/error-identification.html
S08 MDN constraint validation https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Constraint_validation
S09 Settings schema drift Local: docs/settings-schema.md
S10 TM GM API docs https://www.tampermonkey.net/documentation.php
S11 VM GM API docs https://violentmonkey.github.io/api/gm/
S12 ScriptCat GM docs https://docs.scriptcat.org/en/
S13 Mozilla AMO submission https://extensionworkshop.com/documentation/publish/source-code-submission/
S14 Mozilla library usage https://extensionworkshop.com/documentation/publish/third-party-library-usage/
S15 Mozilla policies (Aug 2025) https://blog.mozilla.org/addons/2025/06/23/updated-add-on-policies-simplified-clarified/
S16 CWS API v2 https://developer.chrome.com/blog/cws-api-v2
S17 Monaco AMD deprecation https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md
S18 Edge port guide https://learn.microsoft.com/en-us/microsoft-edge/extensions/developer-guide/port-chrome-extension
S19 Edge API v1.1 https://learn.microsoft.com/en-us/microsoft-edge/extensions/update/api/using-addons-api
S20 ScriptCat background https://docs.scriptcat.org/docs/change/
S21 @background research Local: RESEARCH_FEATURE_PLAN.md
S22 Navigation API Baseline https://www.infoq.com/news/2026/05/navigation-api-browser/
S23 Navigation API MDN https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API
S24 Monaco ESM (#3908) microsoft/monaco-editor#3908
S25 Chrome browser namespace https://developer.chrome.com/blog/extensions-io-2026
S26 Trusted Types https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API
S27 Script subscriptions Local: CHANGELOG.md 2026-06-03
S28 TM v5.5.0 MCP + policy https://www.tampermonkey.net/changelog.php?locale=en
S29 IDE workflow pain https://www.codestudy.net/blog/how-can-i-develop-my-userscript-in-my-favourite-ide-and-avoid-copy-pasting-it-to-the-tampermonkey-s-editor-every-time/
S30 VM @require-local violentmonkey/violentmonkey#2419
S31 DNR response headers https://developer.chrome.com/docs/extensions/whats-new
S32 Firefox mobile userScripts https://bugzilla.mozilla.org/show_bug.cgi?id=1875475
S33 Sigstore keyless https://docs.sigstore.dev/about/overview/
S34 npm Trusted Publishing https://blog.sigstore.dev/npm-provenance-ga/
S35 Browser Sigstore verifier https://tinfoil.sh/blog/2025-12-18-browser-native-verification
S36 Vitest 4.0 Browser Mode https://vitest.dev/blog/vitest-4
S37 VM GM value sync (#48) violentmonkey/violentmonkey#48
S38 TM WebSocket (#1483) Tampermonkey/tampermonkey#1483
S39 VM GreasyFork publish violentmonkey/violentmonkey#2425
S40 Safari UserScripts https://github.com/quoid/userscripts
S41 Safari lacks userScripts https://developer.apple.com/documentation/safariservices/safari-web-extensions
S42 GreasyFork platform https://github.com/greasyfork-org/greasyfork
S43 CodeTweak AI editor https://github.com/MrBlankCoding/CodeTweak
S44 TM Firefox containers Tampermonkey/tampermonkey#2792
S45 WXT evaluation Local: docs/manifest-generation-design.md
S46 storage.sync quota https://developer.chrome.com/docs/extensions/reference/api/storage
S47 userScripts API https://developer.chrome.com/docs/extensions/reference/api/userScripts
S48 Chrome 138 toggle https://developer.chrome.com/blog/chrome-userscript
S49 MV2 deprecation https://developer.chrome.com/docs/extensions/develop/migrate/mv2-deprecation-timeline
S50 VM update diff (#500) violentmonkey/violentmonkey#500
S51 VM integrity (#1558) violentmonkey/violentmonkey#1558
S52 TM closed-source (#1515) Tampermonkey/tampermonkey#1515
S53 Cyberhaven attack https://www.cyberhaven.com/engineering-blog/final-analysis-chrome-extension-security-incident
S54 Trust Wallet attack https://www.rescana.com/post/trust-wallet-chrome-extension-supply-chain-attack-7-million-cryptocurrency-theft-via-compromised-v
S55 MV3 extensions study https://arxiv.org/abs/2503.04292
S56 Arcanum USENIX 2024 https://www.gatech.edu/news/2024/09/17/study-finds-thousands-browser-extensions-compromise-user-data
S57 Signature-Based SRI https://groups.google.com/a/chromium.org/g/blink-dev/c/QSsuBmjlnfk/m/jZA-M5VhAgAJ
S58 OWASP Extension Sheet https://cheatsheetseries.owasp.org/cheatsheets/Browser_Extension_Vulnerabilities_Cheat_Sheet.html
S59 Mozilla secure ext guide https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Security_best_practices
S60 GH Actions hardening https://docs.github.com/en/actions/security-for-github-actions/security-guides/security-hardening-for-github-actions
S61 EU CRA SBOM https://www.darkreading.com/application-security/sboms-in-2026-some-love-some-hate-much-ambivalence
S62 W3C WebExtensions CG https://www.w3.org/community/webextensions/
S63 uBOL architecture https://deepwiki.com/gorhill/uBlock/10.1-mv3-architecture-overview
S64 Stylus MV3 openstyles/stylus#1761
S65 Playwright ext testing https://playwright.dev/docs/chrome-extensions
S66 Vitest 4.1 https://vitest.dev/blog/vitest-4-1.html
S67 CWS 2025 policies https://developer.chrome.com/blog/cws-policy-updates-2025
S68 Privacy Guides TM https://discuss.privacyguides.net/t/does-tampermonkey-extension-affect-privacy-and-security/16728
S69 HN TM trust https://news.ycombinator.com/item?id=35692540
S70 awesome-userscripts https://github.com/awesome-scripts/awesome-userscripts
S71 VM MV3 PR (#2399) violentmonkey/violentmonkey#2399
S72 Mozilla MV2 support https://www.ghacks.net/2025/02/26/firefox-mozilla-confirms-support-for-classic-extensions-and-manifest-v3-add-ons/
S73 npm worm (Sep 2025) https://www.sygnia.co/threat-reports-and-advisories/npm-supply-chain-attack-september-2025/
S74 MDN Firefox MV3 userScripts https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts
S75 CWS Program Policies MV3 code requirements https://developer.chrome.com/docs/webstore/program-policies/policies
S76 Greasy Fork API and prefilled updates https://greasyfork.org/en/help/api
S77 Tampermonkey external editor FAQ https://www.tampermonkey.net/faq.php?q=Q402
S78 Tampermonkey Editors vscode.dev extension https://chromewebstore.google.com/detail/tampermonkey-editors/lieodnapokbjkkdkhdljlllmgkmdokcm
S79 Local userscript workflow pain https://www.reddit.com/r/tampermonkey/comments/1qrbdeo/develop_userscript_in_vscode_without_manual/
S80 Chrome DNR responseHeaders reference https://developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest
S81 Chrome File System Access API https://developer.chrome.com/docs/capabilities/web-apis/file-system-access
S82 Chrome Offscreen API https://developer.chrome.com/docs/extensions/reference/api/offscreen
S83 Chrome remote-hosted-code guidance https://developer.chrome.com/docs/extensions/develop/migrate/remote-hosted-code
S84 ScriptCat background script docs https://docs.scriptcat.org/en/docs/dev/background/
S85 Chrome Navigation API guide https://developer.chrome.com/docs/web-platform/navigation-api
S86 MDN Navigation navigate event https://developer.mozilla.org/en-US/docs/Web/API/Navigation/navigate_event
S87 Chrome webNavigation extension API https://developer.chrome.com/docs/extensions/reference/api/webNavigation
S88 MDN Navigation API overview https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API
S89 CWS User Data FAQ https://developer.chrome.com/docs/webstore/program-policies/user-data-faq/
S90 MDN File System API overview https://developer.mozilla.org/en-US/docs/Web/API/File_System_API
S91 Chrome File System Access persistent permissions https://developer.chrome.com/blog/persistent-permissions-for-the-file-system-access-api
S92 MDN FileSystemFileHandle https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle
S93 Chrome alarms API https://developer.chrome.com/docs/extensions/reference/api/alarms
S94 MDN Clipboard writeText https://developer.mozilla.org/en-US/docs/Web/API/Clipboard/writeText
S95 MDN URL createObjectURL https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL_static
S96 MDN anchor download https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/a#download
S97 Chrome Storage API managed area https://developer.chrome.com/docs/extensions/reference/api/storage
S98 Chrome managed storage manifest schema https://developer.chrome.com/docs/extensions/reference/manifest/storage
S99 Chrome extension policy deployment verification https://support.google.com/chrome/a/answer/7517624

Research-Driven Additions (2026-06-09)

Items below were identified by exhaustive repo walk + 35+ external sources. Duplicates against existing Now/Next/Later/UC/Rejected tiers were filtered. Each item carries impact (1-5), effort (S/M/L/XL), and tier recommendation.

Appendix: Research-Driven Sources

ID Source URL
R01 ScriptCat HN thread (2026) https://news.ycombinator.com/item?id=45938449
R02 ScriptCat DeepWiki https://deepwiki.com/scriptscat/scriptcat
R03 Chrome IndexedDB storage buckets https://developer.chrome.com/blog/maximum-idb-performance-with-storage-buckets
R04 Chrome IndexedDB compression https://developer.chrome.com/docs/chromium/indexeddb-storage-improvements
R05 EU CRA SBOM timeline https://www.herodevs.com/blog-posts/cra-reporting-obligations-start-september-2026
R06 Cyberhaven supply chain https://www.cyberhaven.com/engineering-blog/final-analysis-chrome-extension-security-incident
R07 Trust Wallet supply chain https://thehackernews.com/2025/12/trust-wallet-chrome-extension-hack.html
R08 Chrome SW lifecycle https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle
R09 Monaco v0.55 LSP https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md
R10 TypeScript 6 features https://pooyagolchian.com/blog/typescript-6-features-2026/
R11 Playwright extension testing https://playwright.dev/docs/chrome-extensions
R12 WCAG 2.2 resize text https://www.w3.org/TR/WCAG22/
R13 Chrome ext supply chain (2026) https://securityboulevard.com/2026/03/the-chrome-extension-backdoor-how-productivity-tools-became-enterprise-attack-vectors/

Appendix: Research-Driven Sources (2026-06-12)

ID Source URL
RD12-01 Tampermonkey MCP server https://github.com/Tampermonkey/tampermonkey-mcp
RD12-02 VM MV3 status (dead on Chrome) violentmonkey/violentmonkey#2340
RD12-03 ScriptCat background scripts https://docs.scriptcat.org/en/docs/dev/background/
RD12-04 Chrome userScripts.execute (135+) https://developer.chrome.com/docs/extensions/reference/api/userScripts
RD12-05 Chrome 138 per-extension toggle https://developer.chrome.com/blog/chrome-userscript
RD12-06 Chrome structured-clone messaging https://developer.chrome.com/blog/structured-clone-messaging
RD12-07 Ed25519 native browser support https://blogs.igalia.com/nicolo/2025/05/21/ed25519-in-all-browsers/
RD12-08 Trust Wallet supply chain attack https://thehackernews.com/2025/12/trust-wallet-chrome-extension-hack.html
RD12-09 36-extension compromise (2025) https://blog.sekoia.io/targeted-supply-chain-attack-against-chrome-browser-extensions/
RD12-10 Trusted Types API baseline https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API
RD12-11 Chrome sidePanel.getLayout (140+) https://developer.chrome.com/docs/extensions/reference/api/sidePanel
RD12-12 OWASP Extension Cheat Sheet https://cheatsheetseries.owasp.org/cheatsheets/Browser_Extension_Vulnerabilities_Cheat_Sheet.html
RD12-13 Chrome SW lifecycle improvements https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle
RD12-14 SemVer 2.0.0 pre-release ordering https://semver.org/#spec-item-11
RD12-15 Monaco v0.55 breaking changes https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md
RD12-16 CWS policy updates 2025 https://developer.chrome.com/blog/cws-policy-updates-2025
RD12-17 Firefox AMO policies (Aug 2025) https://blog.mozilla.org/addons/2025/06/23/updated-add-on-policies-simplified-clarified/
RD12-18 MV2 deprecation timeline https://developer.chrome.com/docs/extensions/develop/migrate/mv2-deprecation-timeline

Appendix: Research-Driven Sources (2026-06-13)

ID Source URL
RD13-01 Chrome 141 Signature-Based SRI https://developer.chrome.com/release-notes/141
RD13-02 WICG Signature-Based SRI spec https://wicg.github.io/signature-based-sri/
RD13-03 Popover API baseline https://developer.chrome.com/blog/introducing-popover-api
RD13-04 CSS Anchor Positioning baseline https://developer.chrome.com/blog/anchor-positioning-api
RD13-05 Firefox 149-152 API changes https://blog.mozilla.org/addons/2026/04/23/webextensions-api-changes-firefox-149-152/
RD13-06 CVE-2026-0628 Gemini panel hijack https://unit42.paloaltonetworks.com/gemini-live-in-chrome-hijacking/
RD13-07 Group-IB 2026 supply chain report https://www.group-ib.com/blog/supply-chain-attack-groups-2026/
RD13-08 PackageGate npm zero-days https://www.securityweek.com/packagegate-flaws-open-javascript-ecosystem-to-supply-chain-attacks/
RD13-09 Chrome 139 MV2 final removal https://developer.chrome.com/docs/extensions/develop/migrate/mv2-deprecation-timeline
RD13-10 Chrome I/O 2026 extensions recap https://developer.chrome.com/blog/extensions-io-2026
RD13-11 LayerX extension security 2026 https://go.layerxsecurity.com/browser-extension-security-report-2026
RD13-12 EU CRA draft guidance (Mar 2026) https://digital-strategy.ec.europa.eu/en/news/commission-publishes-feedback-draft-guidance-assist-companies-applying-cyber-resilience-act
RD13-13 Chrome IndexedDB Snappy compression https://developer.chrome.com/docs/chromium/indexeddb-storage-improvements
RD13-14 W3C WebExtensions WG draft charter https://w3c.github.io/charter-drafts/2025/webextensions-wg.html
RD13-15 Chrome DevTools for Agents v1 https://developer.chrome.com/blog/devtools-for-agents-v1

Appendix: Research-Driven Sources (2026-06-14)

ID Source URL
RD14-01 Tampermonkey changelog https://www.tampermonkey.net/changelog.php
RD14-02 Tampermonkey MCP server https://github.com/Tampermonkey/tampermonkey-mcp
RD14-03 Violentmonkey MV3 issue violentmonkey/violentmonkey#1934
RD14-04 ScriptCat repository https://github.com/scriptscat/scriptcat
RD14-05 ScriptCat documentation https://docs.scriptcat.org/en/
RD14-06 Chrome userScripts API https://developer.chrome.com/docs/extensions/reference/api/userScripts
RD14-07 Chrome userScripts toggle change https://developer.chrome.com/blog/chrome-userscript
RD14-08 Chrome structured-clone messaging https://developer.chrome.com/blog/structured-clone-messaging
RD14-09 Firefox userScripts API https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/userScripts
RD14-10 Firefox WebExtensions API changes 149-152 https://blog.mozilla.org/addons/2026/04/23/webextensions-api-changes-firefox-149-152/
RD14-11 Chrome remote-hosted-code guidance https://developer.chrome.com/docs/extensions/develop/migrate/remote-hosted-code
RD14-12 OWASP Browser Extension Vulnerabilities Cheat Sheet https://cheatsheetseries.owasp.org/cheatsheets/Browser_Extension_Vulnerabilities_Cheat_Sheet.html
RD14-13 Sekoia extension supply-chain report https://blog.sekoia.io/targeted-supply-chain-attack-against-chrome-browser-extensions/
RD14-14 LayerX GhostPoster extension campaign https://layerxsecurity.com/blog/browser-extensions-gone-rogue-the-full-scope-of-the-ghostposter-campaign/
RD14-15 Palant remote-code-ban bypass analysis https://palant.info/2025/01/20/malicious-extensions-circumvent-googles-remote-code-ban/

Research-Driven Additions (2026-06-15)

Items below identified by exhaustive repo walk + 40+ external sources across competitors (Tampermonkey v5.5.0, Violentmonkey MV3 status, ScriptCat v1.4.0-beta.4), Chrome 140-148 APIs, Firefox 149-153, security landscape (NSA MCP guidance, EU CRA Sep 2026, Trusted Types Baseline), and dependency changelogs. Deduplicated against all existing tiers (Now through Rejected) and all prior RD-1..RD-14 additions.

Appendix: Research-Driven Sources (2026-06-15)

ID Source URL
RD15-01 NSA MCP security guidance https://media.defense.gov/2026/Jun/02/2003943289/-1/-1/0/CSI_MCP_SECURITY.PDF
RD15-02 Trusted Types Baseline (Feb 2026) https://web.dev/blog/baseline-digest-feb-2026
RD15-03 esbuild security advisory https://security.snyk.io/package/npm/esbuild
RD15-04 EU CRA Sep 2026 reporting https://anchore.com/sbom/eu-cra/
RD15-05 EU CRA EOL obligations https://www.herodevs.com/blog-posts/cra-reporting-obligations-start-september-2026
RD15-06 Violentmonkey MV3 closure violentmonkey/violentmonkey#1934
RD15-07 VM community MV3 PR closed violentmonkey/violentmonkey#2493
RD15-08 ScriptCat v1.4.0-beta.4 https://github.com/scriptscat/scriptcat
RD15-09 Acorn 8.17.0 strict mode https://github.com/acornjs/acorn
RD15-10 Chrome 148 browser namespace https://developer.chrome.com/docs/extensions/whats-new
RD15-11 Monaco v0.55 LSP namespace https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md
RD15-12 Vitest 4.1 Browser Mode stable https://vitest.dev/blog/vitest-4
RD15-13 CVE-2025-49596 MCP Inspector https://github.com/advisories/GHSA-9crc-q9x8-hgqq
RD15-14 Tampermonkey MCP server https://github.com/Tampermonkey/tampermonkey-mcp
RD15-15 Chrome MV2 final removal https://developer.chrome.com/docs/extensions/develop/migrate/mv2-deprecation-timeline

Appendix: Research-Driven Sources (2026-06-20)

ID Source URL
RD20-01 VM MV3 death (Chrome 150 flag removal) https://developer.chrome.com/docs/extensions/develop/migrate/mv2-deprecation-timeline
RD20-02 VM community MV3 PR closed violentmonkey/violentmonkey#2493
RD20-03 VM stranded user base violentmonkey/violentmonkey#1934
RD20-04 Vitest coverage v8 provider https://vitest.dev/guide/coverage
RD20-05 WCAG 2.1 SC 2.1.1 keyboard https://www.w3.org/WAI/WCAG21/Understanding/keyboard.html
RD20-06 AMO reproducible builds https://extensionworkshop.com/documentation/publish/source-code-submission/
RD20-07 CSS logical properties https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values
RD20-08 ScriptCat export format https://docs.scriptcat.org/en/
RD20-09 Chrome 150 release schedule https://chromiumdash.appspot.com/schedule
RD20-10 CWS screenshot requirements https://developer.chrome.com/docs/webstore/images
RD20-11 Trusted Types MDN https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API
RD20-12 NSA MCP security guidance https://media.defense.gov/2026/Jun/02/2003943289/-1/-1/0/CSI_MCP_SECURITY.PDF

Appendix: Research-Driven Sources (2026-06-21)

ID Source URL
RD21-01 CSS logical properties MDN https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_logical_properties_and_values
RD21-02 chrome.i18n API https://developer.chrome.com/docs/extensions/reference/api/i18n
RD21-03 scripts/check-readme-claims.mjs Local: scripts/check-readme-claims.mjs
RD21-04 WCAG 2.2 SC 2.5.8 Target Size https://www.w3.org/TR/WCAG22/#target-size-minimum
RD21-05 MCPMonkey (VM MCP fork) https://github.com/kstrikis/mcpmonkey
RD21-06 MCP spec 2026-07-28 RC https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/
RD21-07 Firefox 153 userScripts.execute https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/153
RD21-08 Axios npm compromise (2026) https://www.microsoft.com/en-us/security/blog/2026/04/01/mitigating-the-axios-npm-supply-chain-compromise/
RD21-09 Red Hat npm worm (2026) https://www.wiz.io/blog/miasma-supply-chain-attack-targeting-redhat-npm-packages
RD21-10 Mastra npm scope takeover (2026) https://www.microsoft.com/en-us/security/blog/2026/06/17/postinstall-payload-inside-mastra-npm-supply-chain-compromise/

Appendix: Research-Driven Sources (2026-06-20 deep pass 2)

ID Source URL
RD22-01 Chrome sidePanel API (close/onOpened/onClosed) https://developer.chrome.com/docs/extensions/reference/api/sidePanel
RD22-02 Popover API Baseline https://developer.chrome.com/blog/introducing-popover-api
RD22-03 CSS Container Queries guide https://developer.chrome.com/docs/devtools/css/container-queries
RD22-04 Navigation API Baseline https://web.dev/blog/baseline-navigation-api
RD22-05 IndexedDB Snappy compression https://developer.chrome.com/docs/chromium/indexeddb-storage-improvements
RD22-06 VM MV3 death issue violentmonkey/violentmonkey#2340
RD22-07 VM fork proposal violentmonkey/violentmonkey#2341
RD22-08 TM closed-source concerns Tampermonkey/tampermonkey#1515
RD22-09 Privacy Guides TM discussion https://discuss.privacyguides.net/t/does-tampermonkey-extension-affect-privacy-and-security/16728
RD22-10 OrangeMonkey Chrome Stats https://chrome-stats.com/d/ekmeppjgajofkpiofbebgcbohbmfldaf
RD22-11 Cyberhaven supply chain attack https://www.cyberhaven.com/engineering-blog/final-analysis-chrome-extension-security-incident
RD22-12 DarkSpectre/GhostPoster 8.8M users https://thehackernews.com/2025/12/darkspectre-browser-extension-campaigns.html
RD22-13 Stanley MaaS guaranteed CWS publication https://www.bleepingcomputer.com/news/security/new-malware-service-guarantees-phishing-extensions-on-chrome-web-store/
RD22-14 LayerX Extension Security Report 2026 https://go.layerxsecurity.com/browser-extension-security-report-2026
RD22-15 OWASP Browser Extension Cheat Sheet https://cheatsheetseries.owasp.org/cheatsheets/Browser_Extension_Vulnerabilities_Cheat_Sheet.html
RD22-16 Chrome DevTools for Agents v1 https://developer.chrome.com/blog/devtools-for-agents-v1
RD22-17 TM alternatives roundup (absent) https://rigorousthemes.com/blog/best-tampermonkey-alternatives/
RD22-18 Chrome MV2 final removal June 30 https://developer.chrome.com/docs/extensions/develop/migrate/mv2-deprecation-timeline
RD22-19 EU CRA reporting timeline https://anchore.com/sbom/eu-cra/
RD22-20 NSA MCP security guidance https://media.defense.gov/2026/Jun/02/2003943289/-1/-1/0/CSI_MCP_SECURITY.PDF
RD22-21 Trusted Types on YouTube https://developer.chrome.com/blog/trusted-types-on-youtube
RD22-22 Monaco v0.55 breaking changes https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md
RD22-23 Vitest 4.1 test tags https://vitest.dev/blog/vitest-4-1.html
RD22-24 TypeScript 6.0 announcement https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/
RD22-25 Chrome I/O 2026 extensions recap https://developer.chrome.com/blog/extensions-io-2026

Appendix: Research-Driven Sources (2026-06-25)

ID Source URL
RD25-01 Chrome 148 release notes (browser namespace, structured clone) https://developer.chrome.com/release-notes/148
RD25-02 Chrome 149 release notes (WebMCP origin trial) https://developer.chrome.com/release-notes/149
RD25-03 Chrome I/O 2026 extensions recap https://developer.chrome.com/blog/extensions-io-2026
RD25-04 Chrome MV2 final deprecation timeline https://developer.chrome.com/docs/extensions/develop/migrate/mv2-deprecation-timeline
RD25-05 Firefox 149-152 WebExtensions API changes https://blog.mozilla.org/addons/2026/04/23/webextensions-api-changes-firefox-149-152/
RD25-06 Firefox 153 release notes https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/153
RD25-07 CSS Anchor Positioning Baseline https://developer.chrome.com/blog/anchor-positioning-api
RD25-08 CSS Container Queries style queries Baseline (May 2026) https://web.dev/blog/baseline-digest-may-2026
RD25-09 Popover API Baseline Widely Available https://developer.chrome.com/blog/introducing-popover-api
RD25-10 CycloneDX 1.7 (ECMA-424 2nd Ed) https://docs.sbom.observer/release-notes/2026-03-25-cyclonedx-1.7
RD25-11 Vitest CVE-2026-47429 (arbitrary file read) https://github.com/advisories/GHSA-5xrq-8626-4rwp
RD25-12 npm audit: tmp path traversal https://github.com/advisories/GHSA-7c78-jf6q-g5cm
RD25-13 npm audit: undici TLS bypass https://github.com/advisories/GHSA-vmh5-mc38-953g
RD25-14 npm audit: vite NTLMv2 disclosure https://github.com/advisories/GHSA-v6wh-96g9-6wx3
RD25-15 npm audit: ws memory exhaustion https://github.com/advisories/GHSA-96hv-2xvq-fx4p
RD25-16 QuickLens extension supply chain attack https://www.rescana.com/post/quicklens-chrome-extension-supply-chain-attack-cryptocurrency-theft-and-clickfix-malware-campaign-a/
RD25-17 Ownership-transfer permission creep attack class https://pluto.security/blog/chrome-extension-supply-chain-attacks-permission-creep/
RD25-18 CSA MCP Security Best Practices v1 https://labs.cloudsecurityalliance.org/agentic/agentic-mcp-security-best-practices-v1/
RD25-19 MCP-38 threat taxonomy (arxiv) https://arxiv.org/pdf/2603.18063
RD25-20 Tweeks (YC W25) AI userscript generator https://www.tweeks.io/
RD25-21 Tampermonkey MCP server (17 GH stars) https://github.com/Tampermonkey/tampermonkey-mcp
RD25-22 Violentmonkey confirmed dead on Chrome violentmonkey/violentmonkey#1934
RD25-23 OrangeMonkey (2M+ users, VM fork) https://chrome-stats.com/d/ekmeppjgajofkpiofbebgcbohbmfldaf
RD25-24 Cosign v3 Sigstore bundle format https://blog.sigstore.dev/cosign-3-0-available/
RD25-25 WCAG 2.2 SC 2.5.8 Target Size Minimum https://www.w3.org/TR/WCAG22/#target-size-minimum
RD25-26 Firefox adopted stylesheets content script access https://bugzilla.mozilla.org/show_bug.cgi?id=1751346
RD25-27 EU CRA open-source carve-out https://digital-strategy.ec.europa.eu/en/policies/cra-open-source
RD25-28 W3C WebExtensions spec draft (June 5, 2026) https://w3c.github.io/webextensions/specification/

Appendix: Research-Driven Sources (2026-06-25 deep pass)

ID Source URL
RD25D-01 Tampermonkey v5.5.0 changelog https://www.tampermonkey.net/changelog.php
RD25D-02 Tampermonkey MCP server https://github.com/Tampermonkey/tampermonkey-mcp
RD25D-03 Violentmonkey MV3 death (confirmed) violentmonkey/violentmonkey#1934
RD25D-04 ScriptCat v1.4.0 AI Agent + MCP https://github.com/scriptscat/scriptcat/releases/tag/v1.4.0
RD25D-05 ScriptCat documentation https://docs.scriptcat.org/en/
RD25D-06 Tweeks (YC W25) AI userscript https://www.tweeks.io/
RD25D-07 OrangeMonkey (2M+ users) https://chrome-stats.com/d/ekmeppjgajofkpiofbebgcbohbmfldaf
RD25D-08 Greasemonkey v4.14 https://github.com/greasemonkey/greasemonkey
RD25D-09 FireMonkey (userscript+userstyle) https://github.com/erosman/firemonkey
RD25D-10 Userscripts Safari v5.0.0-beta.23 https://github.com/quoid/userscripts
RD25D-11 Chrome MV2 final removal (Chrome 150) https://developer.chrome.com/docs/extensions/develop/migrate/mv2-deprecation-timeline
RD25D-12 Chrome Mutation Events deprecation https://developer.chrome.com/blog/mutation-events-deprecation
RD25D-13 Sanitizer API (setHTML) https://web.dev/articles/sanitizer
RD25D-14 Playwright Trace Viewer https://playwright.dev/docs/trace-viewer
RD25D-15 CodeMirror 6 changelog https://codemirror.net/docs/changelog/
RD25D-16 vite-plugin-monkey (2K stars) https://github.com/lisonge/vite-plugin-monkey
RD25D-17 Chrome userScripts API https://developer.chrome.com/docs/extensions/reference/api/userScripts
RD25D-18 Chrome service worker lifecycle https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle
RD25D-19 Chrome I/O 2026 extensions recap https://developer.chrome.com/blog/extensions-io-2026
RD25D-20 Firefox 153 release notes https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/153
RD25D-21 OWASP Extension Cheat Sheet https://cheatsheetseries.owasp.org/cheatsheets/Browser_Extension_Vulnerabilities_Cheat_Sheet.html
RD25D-22 CSA MCP Security Best Practices v1 https://labs.cloudsecurityalliance.org/agentic/agentic-mcp-security-best-practices-v1/
RD25D-23 Extension supply chain attacks (Sekoia) https://blog.sekoia.io/targeted-supply-chain-attack-against-chrome-browser-extensions/
RD25D-24 Cyberhaven supply chain attack https://www.cyberhaven.com/engineering-blog/final-analysis-chrome-extension-security-incident
RD25D-25 Ownership-transfer permission creep https://pluto.security/blog/chrome-extension-supply-chain-attacks-permission-creep/
RD25D-26 Requestly (HTTP interception + scripts) https://www.requestly.com/
RD25D-27 Automa browser automation https://www.automa.site/
RD25D-28 n8n workflow automation https://n8n.io/
RD25D-29 awesome-userscripts https://github.com/awesome-scripts/awesome-userscripts
RD25D-30 Monaco v0.55 changelog https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md

Appendix: Research-Driven Sources (2026-06-26)

ID Source URL
RD26-01 ScriptCat v1.4.0 stable release https://github.com/scriptscat/scriptcat/releases
RD26-02 Chrome 150 MV2 flag removal https://developer.chrome.com/docs/extensions/develop/migrate/mv2-deprecation-timeline
RD26-03 Chrome 151 MV2 final lockdown https://piunikaweb.com/2026/06/08/chrome-manifest-v2-unpacked-extensions-mac-windows/
RD26-04 Firefox 153 beta release notes https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/153
RD26-05 108 malicious extensions campaign (Socket) https://techretry.com/malicious-chrome-extensions-2026/
RD26-06 DLL side-loading via Chrome enterprise policies https://cybersecuritynews.com/malicious-chrome-extension-uses-native-messaging-host/
RD26-07 Vitest 4.1.9 / 5.0.0-beta.5 https://github.com/vitest-dev/vitest/releases
RD26-08 Monaco v0.55.1 stable / v0.56-dev https://github.com/microsoft/monaco-editor/releases
RD26-09 Vitest CVE-2026-47429 (CVSS 9.8) https://github.com/advisories/GHSA-5xrq-8626-4rwp
RD26-10 W3C WebExtensions WG draft charter https://w3c.github.io/charter-drafts/2025/webextensions-wg.html

Deep Audit Findings (2026-07-02)

Verified-but-unfixed items from the 2026-07-02 deep audit. The audit shipped fixes for GM handler auth binding, attribute-injection XSS, the Chrome-as-Firefox misdetection, editor keystroke/cursor/undo bugs, trash-restore + backup-import data loss, and the cloud-sync tombstone-resurrection data loss (see CHANGELOG v3.16.0). The items below were confirmed reachable but deferred as higher-risk or larger than an audit fix.

P2

P3

Research-Driven Additions

Added 2026-07-01. Items below are net-new from the 2026-07-01 research pass and do not duplicate the existing Now/Next/Later/N-/X-/L-/UC- items. Sources in the Research-Driven Sources (2026-07-01) appendix.

P2

P3

Appendix: Research-Driven Sources (2026-07-01)

ID Source URL
RD27-01 Tampermonkey 5.5.0 changelog https://www.tampermonkey.net/changelog.php
RD27-02 Tampermonkey MCP server https://github.com/Tampermonkey/tampermonkey-mcp
RD27-03 ScriptCat v1.4.0 changelog (once/MCP/@early-start) https://docs.scriptcat.org/en/docs/change/
RD27-04 Userscripts for Safari (directory store) https://github.com/quoid/userscripts
RD27-05 TM scripts silently not executing Tampermonkey/tampermonkey#2536
RD27-06 TM document-start injection timing Tampermonkey/tampermonkey#2086
RD27-07 quoid/userscripts instant injection quoid/userscripts#459
RD27-08 GreasyFork SPA navigation warning https://greasyfork.org/en/discussions/development/247083
RD27-09 Chrome 138 Allow-User-Scripts toggle https://developer.chrome.com/blog/chrome-userscript
RD27-10 HTML Sanitizer API (Element.setHTML) https://developer.mozilla.org/en-US/docs/Web/API/HTML_Sanitizer_API
RD27-11 Compression Streams API (Baseline 2025-11) https://developer.mozilla.org/en-US/docs/Web/API/Compression_Streams_API
RD27-12 vite-plugin-monkey dev server HMR https://github.com/lisonge/vite-plugin-monkey
RD27-13 TM granular host-permission requests Tampermonkey/tampermonkey#640
RD27-14 HN Tampermonkey alternatives (privacy) https://news.ycombinator.com/item?id=22896078
RD27-15 CSA MCP security best-practices https://labs.cloudsecurityalliance.org/agentic/agentic-mcp-security-best-practices-v1/

Research-Driven Additions (2026-07-02 research pass)

Net-new from the 2026-07-02 pass (v3.16.0). Verified as not already implemented in code and not duplicating the Next tier, the Deep Audit Findings (2026-07-02), or the 2026-07-01 Research-Driven Additions. Cross-references: the Next-tier "editor cursor position stuck at Ln 1, Col 1" item is RESOLVED in v3.16.0 (monaco-adapter now caches the real cursor) — treat as done. One-click GreasyFork/OpenUserJS publish (VM #2425) is already covered by X-9 (publish handoff) — not re-added. UC-3 (AI-Assisted Script Editing) precondition "on-device LLMs practical" is now MET (Chrome Prompt API stable for extensions since Chrome 138) — see the P2 AI item below to promote it.

P2

P3

Appendix: Research-Driven Sources (2026-07-02)

ID Source URL
RD28-01 TM per-script isolated cookie jars Tampermonkey/tampermonkey#2815
RD28-02 TM fake crypto-exploit userscripts Tampermonkey/tampermonkey#2783
RD28-03 TM streaming GM.fetch request Tampermonkey/tampermonkey#1278
RD28-04 TM MV3 GM_xhr DNR serialization Tampermonkey/tampermonkey#2215
RD28-05 ScriptCat AI Agent scriptscat/scriptcat#1324
RD28-06 ScriptCat request-scoped DNR scriptscat/scriptcat#1377
RD28-07 ScriptCat v1.4.0 release notes https://github.com/scriptscat/scriptcat/releases/tag/v1.4.0
RD28-08 GreasyFork SRI enforcement request greasyfork-org/greasyfork#1070
RD28-09 Chrome Prompt API (Gemini Nano) https://developer.chrome.com/docs/ai/prompt-api
RD28-10 Chrome built-in AI overview https://developer.chrome.com/docs/ai/built-in
RD28-11 VM restrict-to-current-site cluster violentmonkey/violentmonkey#2410
RD28-12 GreasyFork account-takeover propagation greasyfork-org/greasyfork#682
RD28-13 VM local-directory sync violentmonkey/violentmonkey#2125
RD28-14 VM git-server sync violentmonkey/violentmonkey#2176
RD28-15 File System Observer API https://developer.chrome.com/blog/file-system-observer
RD28-16 VM hold-execution-until-sync violentmonkey/violentmonkey#2067
RD28-17 TM partitioned-cookie/CHIPS download Tampermonkey/tampermonkey#2419
RD28-18 TM SR-inaccessible delete controls Tampermonkey/tampermonkey#2813
RD28-19 TM Firefox container force-enable Tampermonkey/tampermonkey#2792
RD28-20 Same-document View Transitions Baseline https://web.dev/blog/same-document-view-transitions-are-now-baseline-newly-available

Research-Driven Additions (2026-07-09 research pass)

Net-new from the 2026-07-09 pass. Deduped against ROADMAP.md, Roadmap_Blocked.md, RESEARCH.md, README v3.18.0, and existing RD28 sources.

P1

P3

Appendix: Research-Driven Sources (2026-07-09)

ID Source URL
RD29-01 ScriptVault support matrix generator scripts/generate-browser-support-matrix.mjs
RD29-02 ScriptVault trust receipts src/background/trust-receipt.ts
RD29-03 ScriptVault local workspace flow tests/e2e/local-workspace.spec.js
RD29-04 Violentmonkey open issue list https://github.com/violentmonkey/violentmonkey/issues
RD29-05 Violentmonkey publish handoff issue violentmonkey/violentmonkey#2425
RD29-06 awesome-userscripts catalog list https://github.com/awesome-scripts/awesome-userscripts
RD29-07 Greasy Fork multi-catalog search script https://greasyfork.org/en/scripts/9630-greasy-fork-search-scripts-on-other-sites-added-more-sites
RD29-08 Violentmonkey external editor/local tracking https://violentmonkey.github.io/posts/how-to-edit-scripts-with-your-favorite-editor/
RD29-09 Violentmonkey metadata block https://violentmonkey.github.io/api/metadata-block/
RD29-10 Tampermonkey 5.5 changelog https://www.tampermonkey.net/changelog.php
RD29-11 Tampermonkey MCP server https://github.com/Tampermonkey/tampermonkey-mcp
RD29-12 ScriptCat repository https://github.com/scriptscat/scriptcat
RD29-13 ScriptCat docs https://docs.scriptcat.org/en/
RD29-14 WXT cross-browser support https://wxt.dev/
RD29-15 Extension.js MV3 framework https://extension.js.org/
RD29-16 Plasmo extension framework https://www.plasmo.com/
RD29-17 MDN cross-browser extension guide https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Build_a_cross_browser_extension
RD29-18 Firefox 153 WebExtension release notes https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/153
RD29-19 Chrome userScripts API https://developer.chrome.com/docs/extensions/reference/api/userScripts
RD29-20 Mozilla web-ext https://github.com/mozilla/web-ext
RD29-21 CSA MCP security best practices https://labs.cloudsecurityalliance.org/agentic/agentic-mcp-security-best-practices-v1/
RD29-22 MCP Inspector RCE CVE-2025-49596 https://www.oligo.security/blog/critical-rce-vulnerability-in-anthropic-mcp-inspector-cve-2025-49596
RD29-23 Vitest 4.1 browser trace view https://vitest.dev/blog/vitest-4-1.html
RD29-24 File System Observer API https://developer.chrome.com/blog/file-system-observer
RD29-25 Monaco editor changelog https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md

Research-Driven Additions

P1

P3

Research-Driven Additions (2026-07-16)

Net-new from the 2026-07-16 pass (baseline v3.20.0). The prior RESEARCH.md's 10 opportunities all shipped (telemetry auth, vendor-import quarantine, URL minimization, typed dispatch, source maps, WCAG 2.2 gate, locale/plural catalogs, fail-closed smokes, unified release preflight, TS7). A competitive sweep confirmed the 2026-H2 GM-API deltas competitors added — GM_audio, request-scoped cookie partitioning + anonymous downloads, @unwrap, Navigation-API onurlchange, CAT_userConfig, on-disk auto-reload (FileSystemObserver) — are ALREADY shipped (verified in wrapper-builder.ts/parser/core.ts/dashboard.js), so they are not re-added. SBOM (check-cra-sbom.mjs) already exists — not re-added. Items below are verified against source and deduped against ROADMAP.md, Roadmap_Blocked.md, CLAUDE.md audit history, and RESEARCH.md rejected ideas.

Under Consideration (2026-07-16)

  • ScriptCat niche directivesCAT_fileStorage (per-script file storage), @storageName (shared cross-script namespace), @definition (.d.ts editor hints), @early-start. Verified absent; low demand and marginal over Chrome's existing document_start + shipped lib/scriptvault.d.ts. Reconsider on user signal. Source: docs.scriptcat.org/docs/dev/meta.
  • On-device "explain this script" via Chrome Prompt API (Chrome 148 stable) — could summarize what an installed/updating script does using the on-device model, strictly opt-in and local. Philosophy tension with any data flow; gate behind explicit per-use consent. Source: https://developer.chrome.com/docs/ai/prompt-api

Research-Driven Additions (2026-07-22)

Net-new from the 2026-07-22 research pass (baseline v3.22.0). Verified against source and deduped against ROADMAP.md, Roadmap_Blocked.md, and the shipped v3.21.0/v3.22.0 work. The prior RESEARCH.md top-10 (persistent UserCSS, esbuild CVE bump, SECURITY.md, permission-drift gate, npm-worm hardening, template-token sanitization, backup gzip, update-body AST re-scan) all SHIPPED — not re-added. Firefox 153 shipped 2026-07-21, unblocking four items previously parked in Roadmap_Blocked.md; those are re-surfaced here as actionable (P2 FF153 cluster).

P2

P3

Delta — focused re-pass (2026-07-22)

Additional verified findings from a same-day analyzer/storage/scope re-pass. Not duplicates of the items above.

Audit Findings — 2026-08-02

Verified-by-observation audit pass against v3.23.1. Baseline before this pass: npm run check green (227 files / 2434 tests), npm run smoke:firefox green, npm run smoke:dashboard and npm run smoke:editor green, working tree clean — no pre-existing failures. Live verification used Firefox Developer Edition 154.0b1 via geckodriver 0.37.1 and headless Chromium via the repo's puppeteer-core. Audit-only: no source file was modified.

  • P3 — Pending-updates size cap measures UTF-16 code units, not bytes, and cites a quota that does not apply Category: reliability Where: src/background/core.ts (UpdateSystem._MAX_PENDING_TOTAL_BYTES and the eviction loop); generated at background.core.js:1799 and background.core.js:2149-2155 Problem: The cap added in commit 405a0f6 computes JSON.stringify(normalized).length, which counts UTF-16 code units, and compares it against a budget named _MAX_PENDING_TOTAL_BYTES. For non-ASCII script bodies the real UTF-8 footprint is up to ~3x the measured value, so a store the code believes is 8 MB can exceed 20 MB on disk — the eviction never fires when it is most needed. The warning string also reports the wrong unit. Separately, the justifying comment says "keep the store safely under the default chrome.storage.local 10 MB quota", but both manifests declare unlimitedStorage, so that quota does not apply and the stated rationale is wrong. Evidence: node -e on a representative non-ASCII payload: JSON.stringify length 101 vs Buffer.byteLength(...,'utf8') 281 — a 2.78x undercount. grep -n unlimitedStorage manifest.json manifest-firefox.json confirms the permission is declared in both. Fix: Measure real bytes with new TextEncoder().encode(json).byteLength (the codebase already uses this in _scriptSourceByteLength), and correct the comment to state the actual reason for the bound (bounding service-worker memory and write cost under unlimitedStorage), not a quota that is not in force. Acceptance: A test queues pending updates whose UTF-8 size exceeds the budget while their UTF-16 length does not, and asserts eviction occurs. Confidence: Verified Effort: S

  • P3 — Light-theme skip link falls just under AA contrast Category: a11y Where: pages/dashboard-a11y.js:30-48 — the .a11y-skip-link rule inside the injected STYLES template (color: var(--accent-blue) on background: var(--bg-header)); the element itself is created at pages/dashboard-a11y.js:198 Problem: The "Skip to main content" link renders rgb(37,99,235) on rgb(228,228,228) for a 4.07:1 ratio at 14px, below the 4.5:1 AA requirement for normal text. This is the first control a keyboard user reaches on the page. Evidence: Computed live in the loaded extension in light theme by walking to the first ancestor with an opaque background and applying the WCAG relative-luminance formula: ratio 4.07, required 4.5, font-size 14px. It was the only genuine failure the sweep found in light theme — the other flagged elements were false positives from gradient-backed buttons whose backgroundColor is transparent. Fix: Darken the skip-link foreground in light theme to a blue meeting 4.5:1 against its actual background (or give the link an opaque high-contrast background of its own when focused), using the theme's existing accent tokens rather than a new literal. Acceptance: The focused skip link measures at least 4.5:1 in light theme; the other three themes stay unchanged. Confidence: Verified Effort: S

Unaudited — needs a pass

Scope not covered by the 2026-08-02 pass. Not findings; each needs its own audit.

  • P3 — Unaudited: cloud sync providers end to end against live services Category: testing Where: modules/sync-providers.js, src/modules/sync-providers.ts, modules/cloud-sync.js Problem: The 2026-08-02 pass verified only the WebDAV path that smoke:firefox exercises against a local stub server. Google Drive, Dropbox, OneDrive, Easy Cloud, and S3 were not exercised against real endpoints, so OAuth refresh, quota, and conflict behavior on those providers is unverified by this pass. Evidence: The Firefox smoke's webdav scenario is the only provider with live coverage in the harness output. Fix: Audit each provider's token refresh, error mapping, and 3-way merge against a real or high-fidelity fake endpoint. Acceptance: Each provider has an observed pass/fail record rather than an inference from shared code. Confidence: Needs-repro Effort: L

  • P3 — Unaudited: Monaco editor interaction surface and the DevTools panel in a real DevTools host Category: testing Where: pages/editor-sandbox.html, pages/monaco-adapter.js, pages/devtools-panel.js Problem: smoke:editor was run and passes (overlay geometry, 14 controls hit-tested, close works), but editor behaviors beyond that harness — find/replace history, undo across tab switches, Vim mode, large-file handling — were not exercised. The DevTools panel was loaded as a bare page, where chrome.devtools.inspectedWindow is absent; its real behavior inside an attached DevTools host is unverified. Evidence: The panel rendered its empty state correctly as a standalone page, which does not exercise the inspected-window code paths. Fix: Drive the editor through its documented interactions, and load the DevTools panel through an actual DevTools session. Acceptance: Both surfaces have observed coverage of their primary interactions. Confidence: Needs-repro Effort: M

  • P3 — Unaudited: install/update flow driven from a real .user.js navigation Category: testing Where: pages/install.js, install interception in src/background/core.ts Problem: The install page was audited only in its no-pending-install error state, which is handled well ("No userscript was found ... Download the userscript again from its source page"). The populated review flow — permission rendering, downgrade detection, @require probing, trust-card provenance — was not exercised in this pass. Evidence: Loading pages/install.html directly yields the empty state by design. Fix: Drive a real .user.js navigation through interception into the review UI and audit the populated states, including a malformed and an oversized script. Acceptance: The populated install review has observed coverage across valid, malformed, and downgrade cases. Confidence: Needs-repro Effort: M

Research-Driven Additions

P0

P1

P2

  • P2 — Add a bounded local-file watcher fallback with explicit watcher health Why: FileSystemObserver is optional and its current error path stops watching and falls back to manual refresh; Chrome’s documented API history and unknown/errored event semantics require a polling fallback for reliable local editing. Evidence: pages/dashboard.js localWorkspaceFileObservers and observer error handling; tests/local-workspace-dashboard.test.js; https://developer.chrome.com/blog/file-system-observer; existing File System Access contract https://developer.chrome.com/docs/capabilities/web-apis/file-system-access. Touches: pages/dashboard.js, pages/dashboard.html, src/storage/script-db.ts, src/background/core.ts only where message/state contracts require it, modules/storage.js/generated artifacts, and local-workspace/dashboard tests. Acceptance: When FileSystemObserver is unavailable or reports unknown/errored state, an active bound editor performs debounced, bounded metadata polling and routes detected changes through the existing permission check and review-diff apply flow; unbound/hidden editors do not poll, permission/read/oversize failures are visible, and tests cover unavailable, healthy, unknown, errored, unchanged, changed, and rebind cases. Complexity: M

  • P2 — Add a partial-locale and RTL regression ratchet Why: The locale gate currently proves generated parity but permits eight partial runtime catalogs at roughly 2.2%–6.1% coverage, so new UI can remain English-only or break RTL without failing CI. Evidence: scripts/check-locales.mjs, docs/locale-coverage.md, _locales/*, modules/i18n.js, and the 2026-08-02 locale:check:gate output; localization backlog quoid/userscripts#415; Stylus translation workflow https://github.com/openstyles/stylus. Touches: scripts/check-locales.mjs, docs/locale-coverage.md, tests/check-locales-report.test.js, tests/dashboard-i18n-removal.test.js, tests/sidepanel-rtl-layout.test.js, and the dashboard/popup/sidepanel localization harness. Acceptance: CI fails on a coverage regression or missing locale status, a deterministic pseudo-locale exercises the major dashboard/popup/sidepanel/editor labels, and RTL checks cover direction, accessible names, overflow, and control order while keeping partial translations explicitly labeled rather than requiring immediate full translation. Complexity: M

P3

  • P3 — Make browser-support and release-artifact references version-derived Why: README.md’s generated support matrix and release-preflight example still reference version 3.22.0 and 2026-07-16 while the live manifests are 3.23.1; readme:check passes without checking those generated references. Evidence: README.md:382, README.md:387-391, README.md:591, manifest.json, manifest-firefox.json, scripts/generate-browser-support-matrix.mjs, and scripts/check-readme-claims.mjs. Touches: README.md generated block, scripts/generate-browser-support-matrix.mjs, scripts/check-readme-claims.mjs, release preflight/version checks, and focused documentation tests. Acceptance: Regenerating the support matrix derives the version and verification date from the current release inputs, artifact filenames and examples agree with the same version, historical examples are marked as historical or removed, and the documentation gate fails on stale version/date references. Complexity: S

Audit Findings — 2026-08-06

Deep multi-pass audit against v3.23.1. Baseline: after npm ci, npm run check is green (227 files / 2434 tests, tsc clean, all gates pass) — no pre-existing failures (an earlier red run was only an empty node_modules). npm audit --omit=optional --audit-level=high reports 16 vulns (10 high, 4 critical) — see the still-open P0 "Restore the blocking high-severity dependency-audit gate" above; not re-logged. Findings were traced against the SHIPPED source of truth: src/background/core.ts (inline, generated into background.core.js) and the promoted src/modules/*.ts / src/background/gm-*.ts handlers. Several src/background/*.ts files (wrapper-builder.ts, import-export.ts, update-checker.ts, install-handler.ts, trust-receipt.ts, parser.ts) are UNSHIPPED mirrors — mirror-only issues were dropped or folded into the mirror-drift item (last P3 below).

P2

  • P2 — Restore/import persist their undo receipt only AFTER all mutations, so a service-worker death mid-restore leaves half-restored state with no rollback record Category: correctness Where: src/modules/backup-scheduler.ts:1484-1491,1740-1783 (restoreBackup); src/background/core.ts:5353,5497-5533,5853-5889 (importScripts/importFromZip) Problem: restoreBackup snapshots pre-restore state in memory, runs the full mutation chain (importFromZip → N IDB writes → settings → folders → workspaces), and calls _pushReceipt only at the very end. An MV3 SW can die at any await; on restart the user has mixed restored/pre-restore data and the receipts ledger — the only undo — holds nothing. This is exactly the multi-key-write / SW-died-between-A-and-B class the receipts feature exists to protect against. Evidence: Verified — traced restoreBackup end to end; receipt push is the last step. Fix: Persist the receipt (with snapshot) BEFORE the first mutation, marked pending, and finalize afterward; on scheduler init surface any still-pending receipt as "restore may be incomplete — roll back?". Cheap alt: a restoreInProgress journal key written before mutating. Acceptance: Killing the SW mid-restore leaves a recoverable receipt; a failure-injection test asserts a receipt exists before mutations begin. Confidence: Verified Effort: M

  • P2 — Custom themes and extra presets apply only to the dashboard; popup, side panel, install, and DevTools ignore them Category: visual Where: pages/dashboard-theme-editor.js persists sv_active_custom_theme; only pages/dashboard.js:5667-5695 reads it. pages/popup.js:709-713, pages/sidepanel.js:503-507, pages/install.js:786-791, pages/devtools-panel.js:295-302 set data-theme from settings.layout only and never read sv_active_custom_theme Problem: The Theme Editor's extra presets (nord/dracula/solarized/etc.) and custom themes are stored as CSS-var overrides under sv_active_custom_theme (they leave settings.layout a built-in). The dashboard injects those vars; every other surface renders the base built-in theme, so a user's chosen theme applies to the dashboard alone — a cross-surface inconsistency that undercuts cohesion. Evidence: Verified — grep -rln sv_active_custom_theme pages/ returns only dashboard.js + dashboard-theme-editor.js; the four secondary surfaces read only settings.layout. Fix: Extract a shared applyTheme() (e.g. pages/theme-apply.js) that sets data-theme AND reads+sanitizes sv_active_custom_theme (replicate the dashboard's /^--[\w-]+$/ key regex + [{};]-in-value rejection), called from popup/sidepanel/install/devtools. Also removes the 5× duplicated auto/layout logic. Acceptance: Applying a custom/extra-preset theme then opening popup/sidepanel/install/devtools renders the custom vars; a test asserts each surface reads and sanitizes sv_active_custom_theme. Confidence: Verified Effort: M

  • P2 — The debugger identifies scripts by raw script_<uuid> everywhere a name should appear Category: ux Where: pages/dashboard-debugger.js:298 (console selector option text = id), :425 (variables selector), :386 (live-reload row label), :396 (toggle aria-label) Problem: All four render the internal id ("script_" + crypto.randomUUID()), so users pick between script_3f6a1c2e-… entries and the debugger is effectively unusable with more than one script; screen-reader users hear a UUID. The module receives only ids and never resolves names, though dashboard.js:16085 builds the init options and holds state.scripts with metadata.name. Evidence: Verified — traced id origin (generateId) and all four render sites. Fix: Pass a getScriptName(id) callback in ScriptDebugger.init options and use it for option text, live-reload labels, and aria-labels, falling back to the id. Acceptance: The debugger's selectors and rows show names; a test asserts names render when a resolver is provided. Confidence: Verified Effort: S

  • P2 — Deleting a custom theme is a 16px hover-only control with no confirmation and no undo Category: ux Where: pages/dashboard-theme-editor.js:924-929 (deleteCustomTheme deletes+persists+toasts immediately), CSS :575-593 (.sv-te-delete-custom { width:16px; height:16px; display:none } revealed on hover) Problem: A custom theme is 21+ hand-picked tokens; the delete trigger is a 16×16 CSS-px "x" appearing on hover (below the repo-enforced 24×24 WCAG 2.2 SC 2.5.8 minimum), with no showConfirmModal (available via ScriptVaultDashboardUI.confirm, used by every other module's delete) and no undo. One mis-click destroys the theme permanently. Evidence: Verified — read the delete handler (stopPropagation → deleteCustomTheme directly) and the hover-reveal CSS. Fix: Route through ScriptVaultDashboardUI.confirm('Delete Theme?', …, {tone:'danger'}) like chains/profiles/templates, and raise the button to ≥24px. Acceptance: Deleting a custom theme requires confirmation and the control is ≥24×24; a test asserts the confirm path. Confidence: Verified Effort: S

  • P2 — Both worldId fallback paths silently restore the exact Firefox failure mode af0bfb3 fixed Category: correctness Where: src/background/registration.ts:646-697 (mirror src/background/core.ts:11928-11973) Problem: If configureWorld throws (:654-656) or register rejects with a worldId message (:687-693), the script registers into the SHARED default world with no warning, no _registrationError, no error-log entry — precisely the "scripts 2..n fail silently" state af0bfb3 fixed. The catch is deliberately broad (world-count limit, reserved-id rejection, transient failure all land here). Related: worldId = script.id, and Chrome reserves ids with leading _; imported-backup ids come from the file, so an id starting with _ silently downgrades to the shared world. Evidence: Verified (code path); Likely (which engine hits the fallback determines user impact). Fix: When worldConfigured is false (or the retry fires) on Firefox, record a diagnostic (_registrationWarning/ErrorLog) so a silent single-script-per-page regression is observable; sanitize/prefix the worldId so it can never start with _. Acceptance: A world-config failure is visible in the error log; an id starting with _ still gets an isolated world; tests cover both. Confidence: Likely Effort: M

  • P2 — The Firefox worldId feature probe tests for configureWorld, not for worldId support Category: correctness Where: src/background/registration.ts:147-157 (supportsUserScriptsWorldId returns typeof chrome.userScripts.configureWorld === 'function') Problem: configureWorld shipped in Firefox before per-world worldId support (dated to 153); on 136–152 the probe is true and the code relies on the engine THROWING on the unknown worldId property. If Firefox instead ignores the unknown property, worldConfigured is true, the retry never fires, and every script silently shares one world again — the original bug, on the Firefox range most users are on, with a suite that only proves behavior on 154.0b1. tests/firefox-per-script-world.test.js:44-51 mocks configureWorld as a no-op and asserts the probe returns true — encoding the same assumption under audit. Evidence: Verified (code + test); Likely (needs a Firefox 152 run to close). Fix: Probe the capability, not the symbol — configureWorld({worldId:'sv-probe', messaging:true}) then getWorldConfigurations() (or a one-time two-script co-execution check), cached per session; fall back to the shared world only when the probe proves worldId is absent. Acceptance: On a Firefox build without worldId support the probe returns false and the code degrades knowingly; a capability-probe test covers it. Confidence: Likely Effort: M

P3

  • P3 — The pending-updates count badge/chip renders #93c5fd on a near-white tint = 1.52:1 in light theme Category: a11y Where: pages/popup.html:694-701 (.menu-item-badge, element #pendingUpdatesBadge at :1472) and pages/sidepanel.html:44-51 (.sp-update-chip, element #btnPendingUpdates at :463) Problem: Both use background: rgb(from var(--sv-info) r g b / 0.12) (theme-aware, near-white in light) with hardcoded color:#93c5fd. In light theme --sv-info is #2563eb, so the background is ~rgb(229,236,253) and #93c5fd text measures 1.52:1 — far below AA 4.5:1. These badges show the queued-updates count (un-hidden when >0). Unlike the dashboard's .script-health-badge/.script-tag (which have html[data-theme="light"] overrides), popup/sidepanel have none for these. Evidence: Verified — computed via the WCAG relative-luminance formula (1.52:1); no light-theme override exists for either selector (one #93c5fd per file). Fix: Replace the hardcoded #93c5fd with a theme-aware token meeting 4.5:1 on the tint in all four themes (plain var(--sv-info) is only 4.37:1 in light — use a darker info-on-tint token such as a new --sv-info-strong, or var(--sv-text)). Acceptance: Both badges measure ≥4.5:1 in light theme (other themes unchanged); an assertion added to tests/accessibility-surface-pass.test.js. Confidence: Verified Effort: S

  • P3 — Restore writes an unvalidated folders.json; a non-array value bricks FolderStorage (all folder ops throw) Category: correctness Where: src/modules/backup-scheduler.ts:1671-1716 (writes parsed archive JSON straight to chrome.storage.local.set({ scriptFolders: folders })); src/modules/storage.ts:872-873 (this.cache = (data['scriptFolders'] as Folder[]) || []) Problem: Backups can be arbitrary external files via "import backup ZIP". restoreBackup writes folders.json with no Array.isArray check; FolderStorage.init assigns any truthy non-array (object/string/number) to cache, and every subsequent cache.find/push/filter throws — folder create/update/delete all TypeError until another restore overwrites the key. verifyBackup only checks it parses as JSON, and verify is optional. Same gap for workspaces.json. Evidence: Verified — read the restore write (no validation) and FolderStorage.init (|| [] guards only falsy). Fix: Validate Array.isArray(folders) (and minimally each entry's id/scriptIds) before writing; harden FolderStorage.init to coerce non-arrays to []. Acceptance: Restoring a backup whose folders.json is an object leaves folders empty rather than broken; a test asserts a malformed folders.json is rejected/coerced. Confidence: Verified Effort: S

  • P3 — Imported/restored settings have no key/type allowlist beyond credential redaction, so a hostile backup can flip security gates Category: security Where: src/background/core.ts:4512-4520,5462 (prepareSettingsForPortableImport) and src/modules/backup-scheduler.ts:604-620,1653 (_prepareSettingsForRestore) — both strip only SETTINGS_CREDENTIAL_KEYS then pass the object to SettingsManager.set Problem: JSON import and full restore apply the backup's settings after removing only the ~11 credential keys. Security-posture keys — allowInternalXhr, allowInternalSyncEndpoints, allowHighPrivilegeScriptApis, trustedSigningKeys, deniedHosts, blacklist, scopedHostPermissions — and arbitrary unknown keys are applied verbatim with no type validation. A shared/hostile backup can silently flip SSRF/privilege gates and seed the signing trust store; malformed types (e.g. deniedHosts as a string) flow into consumers. (Prototype pollution is not possible — spread uses CreateDataProperty; verified.) Evidence: Verified — both redaction helpers delete only SETTINGS_CREDENTIAL_KEYS; SettingsManager.set shallow-merges any object. Fix: Validate imported settings against src/config/settings-schema.json: drop unknown keys, type-check known ones, and require explicit confirmation (or always skip) for the security-relevant subset, mirroring the credentials treatment. Acceptance: Importing a backup that sets allowInternalXhr:true/trustedSigningKeys does not silently apply them; a test asserts unknown/security keys are dropped or gated. Confidence: Verified Effort: M

  • P3 — Local-library sha256 is format-checked but never re-verified against the code it labels (false provenance on import) Category: security Where: src/background/local-libraries.ts:78-114 (normalizeLocalLibrarySnapshots accepts any 64-hex sha256 without recomputing over code) + src/background/core.ts:5386-5388 (JSON import runs settings through it) and :13012-13024 (getLocalLibraryRequireScripts embeds code with the stored hash as a #sha256= label) Problem: The hash is decoration on every path except the original createLocalLibrarySnapshot intake. An imported script can carry up to 8×512 KB of arbitrary library code wearing a "reviewed" hash of, say, real jQuery — false provenance in any UI that shows the hash as review evidence. (The script is quarantine-disabled on import, so this is a provenance-integrity gap, not direct execution.) Evidence: Verified — sha256 is recomputed only in createLocalLibrarySnapshot (:62); normalizeLocalLibrarySnapshots only regex-checks format (:87-90). Fix: Recompute sha256 at registration/wrap time and drop mismatched snapshots; or strip localLibraries from imported settings and require re-review. Acceptance: An imported snapshot whose bytes don't match its sha256 is rejected; a test asserts recomputation. Confidence: Verified Effort: S

  • P3 — @require fallback CDNs silently substitute a different library (jQuery core for a plugin) and cache it for 7 days Category: correctness Where: src/background/core.ts:12041-12071 (getFallbackUrls, substring match lowerUrl.includes('jquery')/'gm_config'/'mutation-summary') and :12321 + the cache-store block Problem: A require of https://mysite.com/jquery-plugin-custom.js that 5xx's falls through to https://code.jquery.com/jquery-3.7.1.min.js, and the bytes are cached under the ORIGINAL URL key (memory + chrome.storage.local, 7 days). The script runs jQuery core in place of its plugin with no error. Version sniffing is also wrong (includes('2.') routes jquery-1.12.4.min.js to the jQuery-2 list). Evidence: Verified — getFallbackUrls substring branches and the jQuery CDN fallback arrays read in shipped core.ts. Fix: Apply a fallback only when the requested URL host is the known CDN being replaced (not any URL containing the substring), or drop generic-name fallbacks; fix version sniffing to parse the version token. Acceptance: A custom-named script whose URL merely contains "jquery" is not substituted; a test covers a plugin URL that 5xx's. Confidence: Verified Effort: S

  • P3 — @require/GM_loadScript execute code fetched over plaintext HTTP with optional SRI Category: security Where: src/background/core.ts require/loadScript fetch paths pass ['http:', 'https:'] to InternalHostGuard (e.g. :10139); verifySRI returns true for a missing/md5- hash Problem: Because the fetch happens in the service worker, no mixed-content blocking applies and SRI is opt-in. Any on-path attacker can replace an @require http://… body or a GM_loadScript('http://…') response with arbitrary JS, evaluated in the script's world. Evidence: Verified — require/loadScript/usercss fetch guards pass ['http:', 'https:']; SRI enforcement is opt-in (sri === 'require'). Fix: For http: requires/loadScript, require a verifiable SRI hash (reject un-pinned http:) or surface an install/update warning; prefer upgrading known https mirrors. Acceptance: An un-pinned @require http://… is refused (or warned) at install; a test covers the http-without-SRI case. Confidence: Likely Effort: S

  • P3 — The network XHR proxy drops XMLHttpRequest's static constants (DONE/OPENED/…) Category: correctness Where: shipped background.core.js:15440 (_WrappedXHR.prototype = _OrigXHR.prototype only) vs :15463 (the WebSocket wrapper does Object.assign(_WrappedWS, {CONNECTING…CLOSED})) Problem: After the proxy installs, XMLHttpRequest.DONE/.OPENED/.UNSENT (static) are undefined for every userscript, so the very common xhr.readyState === XMLHttpRequest.DONE comparison silently never matches. The sibling WebSocket wrapper copies its constants, proving the asymmetry. Evidence: Verified — read both wrapper installs in shipped background.core.js. Fix: Object.assign(_WrappedXHR, { UNSENT:0, OPENED:1, HEADERS_RECEIVED:2, LOADING:3, DONE:4 }) after assigning the prototype. Acceptance: XMLHttpRequest.DONE === 4 inside a wrapped script; a wrapper test asserts the constants. Confidence: Verified Effort: S

  • P3 — Storage-cache refresh clobbers GM_setValue/GM_deleteValue writes made during the startup window Category: correctness Where: shipped background.core.js:13593 (_cache = { ..._cache, ...freshValues }) Problem: The comment says "merge fresh values with local changes made before refresh completed", but the spread order lets the (possibly pre-write) background snapshot overwrite them. A GM_setValue during the startup window is persisted but the local cache reverts to the stale value, so subsequent synchronous GM_getValue returns the old value for the page's life; a GM_deleteValue is likewise resurrected in the cache. Evidence: Verified — read the merge line in shipped background.core.js. Fix: Track keys mutated locally before the refresh lands and exclude them from the freshValues overwrite (or spread freshValues first then re-apply the local mutation log). Acceptance: A GM_setValue during startup is readable synchronously afterward; a test drives a write racing the initial fill. Confidence: Verified Effort: S

  • P3 — Notification ownership check fails open and records an unauthenticated owner Category: security Where: src/background/gm-notification-handler.ts:138-146 (callerOwnsNotification returns true when no _notifCallbacks entry exists) and :176,187-193 (id = caller-chosen tag, owner = data.scriptId) Problem: callerOwnsNotification returns true whenever there is no callback record — true for every fire-and-forget script notification AND ScriptVault's own internal notifications (update-available, error digests; ids like update-batch-${Date.now()}/error-${scriptId}-${Date.now()}, brute-forceable within a second). Any script can GM_closeNotification/GM_updateNotification a guessed id to suppress or fully rewrite (arbitrary title/text/image) the extension's own notifications under its identity. The owner is stored as the unauthenticated data.scriptId, and a caller-chosen tag becomes the Chrome id, so two scripts sharing a tag overwrite each other. Evidence: Verified — the fail-open branch is documented in a comment; owner stored as data.scriptId. Fix: Always record an owner (sender.userScriptId || data.scriptId) at create time, namespace caller tags per script (${scriptId}:${tag}), and fail closed when no owner record exists for a caller-supplied id. Acceptance: A script cannot update/close another script's or the extension's notification; a test covers a forged-id update attempt. Confidence: Verified Effort: S

  • P3 — Cookie-routing DNR session rule is not scoped to the extension's own request Category: security Where: src/background/core.ts:4842-4878 (session rule condition { regexFilter: <exact url>, resourceTypes: ['xmlhttprequest'] } — no tabIds/initiatorDomains) Problem: For the lifetime of a cookie-routed GM_xmlhttpRequest, any XHR from any tab to that exact URL gets its Cookie header replaced with the extension-computed value (possibly from a partitioned jar the page cannot otherwise reach). regexFilter is case-insensitive by default, widening the match. A page racing a fetch to the same URL can harvest the injected header server-side or strip its own session cookie. Evidence: Verified — rule construction read; the URL lock serializes ids but not applicability. Fix: Add condition.tabIds: [chrome.tabs.TAB_ID_NONE] (extension-initiated only) or an extension-scoped initiatorDomains, and set isUrlFilterCaseSensitive: true. Acceptance: The cookie-routing rule applies only to the extension's own request; a test asserts the condition scoping. Confidence: Likely Effort: S

  • P3 — .user.js/.user.css URLs with a fragment bypass install interception Category: correctness Where: src/background/core.ts:10176,10217 (JS regex /\.user\.js(\?.*)?$/i + urlMatches filter) and :10253,10272 (.user.css) Problem: webNavigation details.url includes fragments, so https://example.com/x.user.js#anything never matches (the urlMatches filter means the listener isn't even invoked) and the raw script renders in the tab instead of the install review. Same for .user.css. Evidence: Verified — regex evaluated; the RE2 filter gates listener invocation. Fix: Extend both patterns to (\?[^#]*)?(#.*)?$ (JS check and the RE2 urlMatches filter). Acceptance: A .user.js#frag URL is intercepted into the review page; a test covers the fragment case. Confidence: Verified Effort: S

  • P3 — Install trust/provenance records the pre-redirect URL; the final response.url is discarded (source-badge laundering) Category: security Where: src/background/core.ts:10141-10157 (_fetchPendingUserscript stores the navigation URL), :10428-10451 (installFromUrl) Problem: classifyResponseUrl rejects only internal-host redirects; the Source & Trust card, classifyInstallSource badge, and the persisted receipt installUrl all use the PRE-redirect URL. An open redirect on a "good"-tone registry host would display a trusted source for bytes served by an arbitrary external host. fetchScriptPreview already returns finalUrl, so the asymmetry is known. Evidence: Verified — traced pendingInstall.urlinstallSourceUrl → receipt source.installUrl; response.url is never captured on this path. Fix: Record response.url alongside the requested URL in pendingInstall; badge/classify from the final URL and flag a cross-host redirect as a review reason. Acceptance: A cross-host redirect shows the final host (and a warning); a test asserts the receipt records the resolved URL. Confidence: Likely (mechanism verified; end-to-end needs an open redirect on a trusted host) Effort: S

  • P3 — Auto-update re-notifies every cycle for update URLs whose servers send no validators Category: ux Where: src/background/core.ts:2506-2541 (autoUpdate notification gate), :1899 (validators stored only if present), queueUpdates rebuild Problem: When the update endpoint returns no ETag/Last-Modified, every cycle re-fetches, re-detects the same newer version, re-queues it (resetting queuedAt), and — for review-required/non-apply-safe mode — fires "N updates ready" again. The user is re-notified every cycle until they act, and a dismissed pending update from such a server reappears next cycle. Evidence: Verified — traced the no-validator path through queueUpdates to the notification gate queued > 0. Fix: Suppress the notification when the queued set is id+version-identical to what was already pending; preserve original queuedAt for unchanged entries. Acceptance: A validator-less server does not re-notify on an unchanged pending update; a test covers repeated cycles. Confidence: Verified (logic); real-world cadence Needs-repro Effort: S

  • P3 — checkForUpdates doesn't skip user-modified scripts, so they are fetched/receipt-built/queued/notified but can never apply and never clear Category: correctness Where: shipped src/background/core.ts:1859-1866 (skips @nodownload but not settings.userModified) vs :2001 (applyUpdate refuses non-force applies of user-modified); applyPendingUpdate clears the queue entry only on success Problem: A user-modified script with an update URL is fetched every cycle, _buildPendingUpdate runs full trust-receipt construction (network-fetching every @require body), the item is queued (possibly safeToApply), the user is notified, and Apply returns {skipped:'user-modified'} without clearing the queue — so it sits there indefinitely. The tested mirror update-checker.ts:256 DOES have the skip, so the suite pins behavior the runtime lacks. Evidence: Verified — grep userModified in shipped checkForUpdates returns nothing; mirror has the skip. Fix: Add the userModified skip to the live checkForUpdates auto path (keep manual single-script checks reporting it), or mark such items un-appliable and exclude them from the "ready" count. Acceptance: A user-modified script is not re-queued/re-notified on auto-update; a test covers the auto path. Confidence: Verified Effort: S

  • P3 — The primary install path (saveScript) skips ESM bundling, so ESM scripts install unbundled and bypass the experimental-flag refusal Category: correctness Where: src/background/core.ts:6754-6916 (saveScript — no ESMUserscriptBundler.bundleIfNeeded) vs installFromCode (:10298) and applyUpdate (:2001) which call it Problem: saveScript is the path used by the install-review page and the dashboard editor. An @module 1/@inject-into module script is accepted raw: with experimentalESMUserscripts off it installs where the other paths would refuse it, and either way the raw import statements land in the GM wrapper IIFE and fail at injection with no surfaced error. Evidence: Verified — read the full saveScript body; no bundler reference. Fix: Run bundleIfNeeded in saveScript after parse (same pattern as installFromCode). Acceptance: An ESM script installed via the review page is bundled or refused per the flag; a test covers both flag states. Confidence: Verified Effort: S

  • P3 — applyUpdate registers new code before persisting; a persist failure leaves running and stored code divergent Category: correctness Where: src/background/core.ts:2091-2108 (re-register, then ensurePersistentStorageForScriptWrite/ScriptStorage.set) Problem: Register-first is deliberate and registration failure is handled, but the reverse is not: if the persist throws after successful re-registration (e.g. quota), the browser runs the new code while storage holds the old version — dashboard, receipts, and version history disagree with what executes until the next SW restart re-registers the old code (a silent downgrade of running code). Evidence: Verified — read the ordering; no compensating re-register on persist failure. Fix: On persist failure, best-effort re-register the previous script (or unregister) before propagating the error. Acceptance: A persist failure after re-register restores the previously-running code; a test injects a rejecting ScriptStorage.set. Confidence: Verified Effort: S

  • P3 — .user.css pending payloads have no TTL, cap, or dedup — unlike the .user.js path — and leak when the tab never loads the dashboard Category: reliability Where: src/background/core.ts:10249-10262 (writes pendingUserStyle_<tabId>, up to MAX_SCRIPT_SIZE = 5 MB), consumed at pages/dashboard.js:13547-13554; the _storePendingInstall TTL/cap sweep (:10104-10131) covers only pendingInstall* Problem: The .user.css interceptor writes the full untrusted CSS under pendingUserStyle_<tabId> with no TTL, no cap, and no in-flight dedup. The sibling .user.js path has a 5-minute TTL, a 32-entry cap, and a _fetchPendingUserscript in-flight map. Close the redirected tab before the dashboard loads and the entry is permanent; two tabs opening the same .user.css fetch it twice. (Corroborated by two independent passes.) Evidence: Verified — the pending-install sweep prefix does not include pendingUserStyle_; no dedup map on the UserCSS fetch. Fix: Reuse the _storePendingInstall TTL/cap eviction for the pendingUserStyle_ prefix and add the same in-flight dedup as _fetchPendingUserscript. Acceptance: An unconsumed pendingUserStyle_ entry is swept after the TTL and concurrent same-URL opens fetch once; tests cover both. Confidence: Verified Effort: S

  • P3 — EasyCloud debounce/periodic sync paths bypass the connected gate (status churn; possible post-disconnect resurrection) Category: correctness Where: src/modules/sync-easycloud.ts:1185-1192 (notifyScriptSaved/notifyScriptDeleted_debouncedSync), _handleAlarm_performSync (:980-1005) never checks KEYS.CONNECTED; contrast the gated storage listener :1220-1223; disconnect revoke is fire-and-forget .catch(()=>{}) (:1378-1382) Problem: For never-connected users, every script save schedules a doomed sync 5 s later that fails token acquisition and persists easycloud_status='error' (churn). Worse edge: after disconnect(), if the revoke fetch fails, the Google grant survives, getAuthToken(false) still mints tokens, and the next save silently resumes uploading to Drive after the user disconnected. Evidence: Verified — traced notifyScriptSaved_debouncedSync_handleAlarm_performSync; no KEYS.CONNECTED read on that path. Fix: Read KEYS.CONNECTED at the top of _performSync (or _handleAlarm) and return early when false. Acceptance: Saves while disconnected schedule no sync; a test asserts _performSync early-returns when disconnected. Confidence: Verified (churn); Likely (resurrection) Effort: S

  • P3 — Refresh-token failure no longer clears stale tokens (regression vs the documented v2.0.2 fix) Category: reliability Where: src/modules/sync-providers.ts:975-978,1413-1416,1764 (Google/Dropbox/OneDrive refresh only log + return null; connected flags stay true) Problem: CLAUDE.md records a v2.0.2 fix "clear stale tokens on 400/401 refresh failure". The current TS source and generated modules/sync-providers.js only log and return null; with a revoked refresh token every cycle burns a probe+refresh and fails generically, googleDriveConnected etc. stay true, and nothing tells the user to reconnect. With the autoSync-persistence gap above, the failure is fully silent. Evidence: Verified — behavior read in TS + generated runtime; intent regression inferred from the changelog note. Fix: On a definitive invalid_grant/400/401 refresh, clear the dead tokens and flip the provider's connected flag to "reconnect required"; keep network-level nulls non-destructive. Acceptance: A revoked refresh token flips the provider to disconnected and surfaces "reconnect"; a test covers invalid_grant. Confidence: Verified Effort: S

  • P3 — No 429/Retry-After handling; Drive rate-limit 403 is misread as an auth failure Category: reliability Where: src/modules/sync-providers.ts:1009-1011 and all providers Problem: No provider inspects 429/Retry-After; a rate-limited upload throws generically and the fixed-period autoSync alarm retries at the same cadence (no backoff). Google Drive signals rate limiting with HTTP 403 (userRateLimitExceeded), and googledrive.getValidToken treats 403 as expired-token → a pointless refresh per cycle while rate-limited. Evidence: Verified — code paths read; Drive's 403-for-quota is documented API behavior. Fix: Distinguish 403 rate-limit bodies from auth 403s before refreshing; honor Retry-After and back off the autoSync alarm after consecutive failures. Acceptance: A 429/403-quota response backs off rather than refreshing every cycle; a test covers a rate-limit body. Confidence: Verified Effort: M

  • P3 — Encrypt-side sync KDF iterations are uncapped while decrypt caps at 10M, so a large configured value produces undecryptable envelopes Category: correctness Where: src/modules/sync-crypto.ts:97-102 (resolveIterations, no upper bound) vs :221-225 (decrypt rejects > MAX_KDF_ITERATIONS = 10M); setting at settings-defaults.json:34 + the Settings type (reachable via settings import) Problem: A settings import setting syncEncryptionKdfIterations to e.g. 20M produces uploads every device — including the author — refuses with "out-of-range KDF iteration count", bricking sync until the remote blob is manually deleted. Evidence: Verified — both bounds read; the setting is reachable via import. Fix: Clamp resolveIterations to [floor, MAX_KDF_ITERATIONS]. Acceptance: A configured value above the cap is clamped and the envelope decrypts; a test covers an over-cap value. Confidence: Verified Effort: S

  • P3 — 30-day tombstone prune is defeated by the remote round-trip; the uploaded tombstone map grows unboundedly Category: maintainability Where: src/background/cloud-sync.ts:1665 and src/modules/sync-easycloud.ts:869 (union remote tombstones unconditionally), upload includes the full merged map (:1872); local prune core.ts:10975-10986 Problem: The maintenance prune removes local tombstones >30 days, but the next sync unions the remote envelope's tombstones back in, detects a change, re-persists the pruned entries, and re-uploads them. Nothing prunes tombstones from the uploaded envelope, so the local prune is a no-op and the map grows with every deletion for the account's life (inflating every future encrypted payload). Evidence: Verified — both engines union remote tombstones; upload includes the full map. Fix: Apply the same 30-day age filter to the merged tombstones before persisting/uploading (safe: the resurrection guard is timestamp-based). Acceptance: Tombstones older than 30 days stop reappearing after a sync; a test covers prune survival across a round-trip. Confidence: Verified Effort: S

  • P3 — matchPattern diverges from Chrome on scheme wildcard and host case (over-counts, needless reloads, missed matches) Category: correctness Where: src/background/url-matcher.ts:191 (scheme === '*') and :203-212 (case-sensitive host) Problem: Used for badge counts, popup listing, autoReloadMatchingTabs, and MatchSet.getMatching. (a) scheme === '*' accepts ANY protocol, so @match *://*/* reports a match on file:///ftp:///chrome-extension://; Chrome's * means http/https only → over-counted badges and needless chrome.tabs.reload of file:// tabs on save (tab-reload.ts:41-45). (b) Host comparison is case-sensitive against the already-lowercased urlObj.hostname while isValidMatchPattern accepts uppercase, so @match *://GitHub.com/* runs in Chrome (which canonicalizes) but ScriptVault's matcher says "no match" → the popup shows no scripts and auto-reload skips the page. Evidence: Verified — both divergences read in the shipped matcher. Fix: Restrict * to http/https (keep @include's broader semantics in matchIncludePattern), and lowercase the pattern host before comparison. Acceptance: @match *://*/* does not match file://; @match *://GitHub.com/* matches github.com; tests cover both. Confidence: Verified Effort: S

  • P3 — The badge counts scripts that can never run on the page Category: correctness Where: src/background/badge.ts:88-98 (matchingScripts = script.enabled && doesScriptMatchUrl(...)) Problem: The count does not exclude the classes registerScript refuses: meta.background scripts (registration.ts:331-337), @run-at context-menu (:543-548), settings._importQuarantine (:192-194), or scripts left with _registrationError. A user with a couple of background/context-menu scripts sees a permanently inflated "running here" count. Evidence: Verified — read the counting predicate vs the registration exclusions. Fix: Filter through isScriptEligibleForRegistration plus the background/context-menu exclusions before counting. Acceptance: Background/context-menu/quarantined scripts are not counted as running on a page; a test covers the exclusions. Confidence: Verified Effort: S

  • P3 — key in meta walks the prototype chain in the UserCSS metadata parser Category: correctness Where: src/modules/userstyles.ts:545-547 (else if (key in meta) { meta[key] = value; }) Problem: key in meta is true for every Object.prototype member, so @toString evil in a ==UserStyle== block stores an own toString string on the persisted meta; any later string coercion throws. Impact is small (nothing currently coerces it) but the guard is not doing what it reads as. (Same class as the GM_getValue proto item.) Evidence: Verified — read the parse branch. Fix: Object.prototype.hasOwnProperty.call(meta, key). Acceptance: @toString/@constructor directives are ignored; a parser test covers them. Confidence: Verified Effort: S

  • P3 — executeMenuCommand forwards a caller-supplied scriptId, letting one script trigger another's menu-command callback Category: security Where: src/background/gm-menu-handler.ts:146-153 (passes data.scriptId to chrome.tabs.sendMessage; every other branch uses ownedScriptId) Problem: Unlike the sibling branches, this trusts data.scriptId, so a script can fire another script's registered menu-command callback in the shared tab. (Also: menuCommands read-modify-write via chrome.storage.session at :84-119 is unserialized and keys under the literal "undefined" when ownedScriptId is absent.) Evidence: Verified — read the handler; the asymmetry with ownedScriptId is clear. Fix: Use ownedScriptId/sender.userScriptId for the target; serialize the menuCommands read-modify-write and skip when the owner id is undefined. Acceptance: A script cannot trigger another script's menu command; a test asserts owner scoping. Confidence: Verified Effort: S

  • P3 — GM_audio watch state is per-tab, not per-script — one script's unwatch stops events for all Category: correctness Where: src/background/gm-audio-handler.ts:86-101 (_audioWatchedTabs is a Set<number>) Problem: The first script to call GM_audio_unwatchState stops state events for every other script watching that tab. Evidence: Verified — read the handler; watch state keyed by tab only. Fix: Key watch state by ${scriptId}:${tabId} and emit per-script. Acceptance: Two scripts watching one tab's audio are independent; a test covers isolation. Confidence: Verified Effort: S

  • P3 — __svEventHandler accepts cross-frame postMessage (missing the event.source !== window guard its three siblings have) Category: security Where: shipped background.core.js:14910 (window.addEventListener('message', function __svEventHandler(event) {…} with no source guard) vs the guarded handlers at :13347,13483,13556 Problem: Any window holding a handle (opener, embedding frame) can inject notificationEvent/downloadEvent/openedTabClosed and fire the script's onclick/onload/onclose callbacks. CHANNEL_ID derives from the public extension id; the required per-install scriptId is leaked into the page DOM by GM_addStyle (style.setAttribute('data-scriptvault', scriptId)), so a same-window page script can read it and forge messages. Evidence: Verified — the three siblings guard event.source !== window; __svEventHandler at :14910 does not. Fix: Add if (event.source !== window) return; to __svEventHandler; stop stamping the raw scriptId into the page DOM (use a non-reversible marker). Acceptance: A cross-frame forged event does not fire the script's callbacks; a test covers a foreign event.source. Confidence: Verified Effort: S

  • P3 — Offscreen document accepts analysis/merge work from any extension context (content scripts, userscripts) with no input size bounds Category: security Where: offscreen.js:10-33 (gate is only _sender.id !== chrome.runtime.id); handleAnalyze/handleMerge/handleDiff/handleESMImports have no length caps Problem: chrome.runtime.sendMessage reaches all extension contexts, so {type:'offscreen_analyze', code:<pathological>} from a content-script/userScript-world sender reaches handleAnalyze even though the SW's action gate would reject the same message. A full Acorn parse + walkAST (or Diff.structuredPatch) on multi-MB inputs is CPU/memory abusable. Evidence: Verified — the only gate is the id check; no size caps in the handlers. Fix: Require a nonce or verify _sender.url is the SW/extension page (content-script senders carry an http(s) sender.url and a sender.tab), and cap input lengths before parsing. Acceptance: An oversized/foreign offscreen request is rejected; a test covers a content-script-shaped sender and an oversized input. Confidence: Verified (reachability); Needs-repro (practical impact) Effort: S

  • P3 — Messaging-failure catch branches discard e.message, leaving no actionable detail on SW-asleep/port-closed failures Category: maintainability Where: pages/dashboard.js:12587,13015,13121,13198,15338,16406 (catch (e) toasts a bare fallback like "Rollback failed"/"Update failed") Problem: The sibling res?.error || 'X failed' path surfaces background-reported errors, but the catch (e) branches drop e.message; on a rejection the user gets no detail. btnCreateBackup (:17468) and the save path already preserve detail. Evidence: Verified — read all six sites. Fix: showToast(e?.message || 'X failed', 'error') in the catch branches. Acceptance: A rejected background call shows its message; consistent with the save/backup handlers. Confidence: Verified Effort: S

  • P3 — CSP reporter's "workarounds" recommend approaches that cannot work in this product Category: ux Where: pages/dashboard-csp.js:30-52 Problem: The connect-src suggestion ships chrome.runtime.sendMessage({ action: 'fetch', url }) — no fetch action exists in the router and the userscript allowlist would reject it — and the script-src suggestions (chrome.scripting.executeScript, content-script new Function) are extension-developer techniques unavailable to userscript authors. Users following the product's own advice hit dead ends. Evidence: Verified — action:'fetch' is the only sent action string with no router entry. Fix: Replace with ScriptVault-actionable guidance (GM_xmlhttpRequest + @grant/@connect, GM_addElement, @inject-into notes) and drop the extension-dev samples. Acceptance: The CSP workaround cards reference only actions/APIs available to userscripts. Confidence: Verified Effort: S

  • P3 — Live Reload empty state tells end users to call an internal JS function Category: docs Where: pages/dashboard-debugger.js:380 ('No scripts registered. Call enableLiveReload(scriptId) to add one.') Problem: enableLiveReload is a module API, not a UI action; developer copy leaked into a user-facing empty state, and the real path (open the debugger from a script's editor) is never mentioned. Evidence: Verified — read renderLiveReloadPanel; population is via the editor debug button / console ingestion. Fix: Replace with e.g. "No scripts captured yet. Open the debugger from a script's editor to add it here." Acceptance: The empty state describes an in-UI action. Confidence: Verified Effort: S

  • P3 — Dependency graph has no zero-scripts empty state (blank canvas) Category: ux Where: pages/dashboard-depgraph.js:576-620 (render() has no nodes.length === 0 path); the only empty copy is the sidebar's "Select a node…" Problem: A user with no scripts (or no relationships) opening Utilities → Dependency Graph sees an unexplained empty canvas. Evidence: Verified — read render(); dg-empty is sidebar-only. Fix: When nodes.length === 0, overlay "No scripts to graph yet — install a script to see its dependencies and match overlaps." Acceptance: The empty graph shows an explanatory message. Confidence: Verified Effort: S

  • P3 — Drifted UNSHIPPED mirror modules are pinned by the test suite as if they were the product Category: maintainability Where: src/background/wrapper-builder.ts, import-export.ts, update-checker.ts, install-handler.ts, trust-receipt.ts, parser.ts (none in ts-source-promotion.json; live copies are inline in core.ts); tests tests/gm-websocket.test.js, wrapper-gm-tabs-39-13.test.js, wrapper-dom-security.test.js, pending-update-queue.test.js, pending-install-isolation.test.js, trust-receipt*.test.js, versions.test.js and others import the mirrors Problem: These files are re-exported only by the unused src/background/index.ts barrel and are not built. They have measurably drifted from the live core.ts: mirror wrapper-builder.ts lacks GM_getTab/saveTab/getTabs grant checks (live core.ts has them), defaults GM_getResourceURL to a never-revoked blob URL (live uses the data-URI leak fix), is missing standalone GM_updateNotification/GM_closeNotification + buttons[]/onbuttonclick + the highlight-path _notifCallbacks cleanup, and meta.grant.length throws where core uses meta.grant || ['none']; mirror import-export.ts lacks versionHistory snapshot, import receipts, per-script settings preservation, and resets createdAt on overwrite; mirror update-checker.ts lacks the backoff engine, the pending-updates byte cap, and the provenance-failure gate (but has the userModified skip the runtime lost). Tests importing these prove nothing about shipped behavior (the "check wired to the wrong data source" failure mode) and mislead anyone who "fixes" a bug in the mirror. GM_webSocket is concrete: implemented in the mirror wrapper and covered by tests/gm-websocket.test.js, but the shipped injected wrapper exposes no window.GM_webSocket/GM.webSocket (grep "window.GM_webSocket" background.js → 0), so a @grant GM_webSocket script gets a ReferenceError. Evidence: Verified — ts-source-promotion.json has no entries for these files; live-vs-mirror drift confirmed feature-by-feature (consolidates three passes' observations); grep "window.GM_webSocket" background.js → 0. Fix: For each mirror either (a) promote it — extract the live logic from core.ts into the module, add it to ts-source-promotion.json + the drift gate — or (b) delete it and repoint its tests at the generated background.core.js extraction. At minimum add a drift assertion between each mirror and its inline core.ts copy so divergence fails CI. Separately decide whether GM_webSocket is a shipped feature; if so expose the client in the live wrapper, else drop the mirror + test. Acceptance: No test imports a non-promoted src/background/*.ts mirror without a drift assertion against the shipped copy; GM_webSocket either works in a live browser or is removed from docs/tests. Confidence: Verified Effort: L

  • P3 — Doc rot: CLAUDE.md lists dashboard modules that do not exist Category: docs Where: repo CLAUDE.md "Dashboard Modules (26 files)" lists dashboard-i18n-v2.js and dashboard-recommendations.js Problem: Neither file exists in pages/ and nothing references I18nV2; future maintainers/agents hunt for phantom files. Evidence: Verified — ls fails for both; grep for references returns nothing. Fix: Remove the two entries and re-count the module list. (CLAUDE.md is AI working notes, not user-facing; safe to edit.) Acceptance: The module inventory lists only files that exist. Confidence: Verified Effort: S 2026-08-06 research pass — same item, wider scope than first recorded. The same file also states version v3.19.2 (actual 3.24.0), Monaco 0.55.1 (actual 0.56.0), vendored acorn 8.14.1 and diff 7.0.0 — both wrong: lib/acorn.min.js is 8.17.0 and lib/diff.min.js is 9.0.0, correctly recorded in docs/amo-vendored-libraries.md. Fold these into the same sweep; the vendored ones matter most because a stale note invites a needless "upgrade the vendored library" task that is already done.

Unaudited — needs a pass

  • P3 — Unaudited: live-browser drive of the populated install review, editor interactions, and DevTools panel in a real host Category: testing Where: pages/install.js (populated states), pages/monaco-adapter.js/pages/editor-sandbox.html, pages/devtools-panel.js Problem: This pass was static/trace + Node-timing based; the populated install review (valid/malformed/oversized/downgrade), editor find/replace/undo-across-tabs/large-file behavior, and the DevTools panel inside an attached DevTools session were not driven live. (Overlaps the prior 2026-08-02 "Unaudited" items; still open.) Evidence: No live-drive harness was run in this pass. Fix: Drive a real .user.js navigation into the review UI and load the DevTools panel through an attached session; drive the editor's documented interactions. Acceptance: Observed coverage of the populated install/editor/devtools interactions. Confidence: Needs-repro Effort: M

  • P3 — Unaudited: cloud providers against live endpoints, and Stylus import Category: testing Where: src/modules/sync-providers.ts (Google Drive/Dropbox/OneDrive/S3 live OAuth+quota+429 bodies), src/modules/sync-easycloud.ts, _convertStylusStyle/importStylusBackup in src/modules/userstyles.ts Problem: Provider findings above were traced statically; real OAuth consent, token refresh, quota, and 429/403 bodies were not exercised against live services. Stylus import conversion was not audited. Evidence: No live endpoint or Stylus-import fixture was run in this pass. Fix: Audit each provider against a real/high-fidelity fake endpoint; add Stylus-import conversion fixtures. Acceptance: Each provider and the Stylus import path has an observed pass/fail record. Confidence: Needs-repro Effort: L

Research-Driven Additions (2026-08-06 research pass)

Net-new from the 2026-08-06 external research pass (baseline v3.24.0, commit 13e63d6). Deduped against every existing section of this file and against Roadmap_Blocked.md. The prior pass's P0 (blocking high-severity dependency audit) shipped in 68d2001 and npm audit --omit=optional --audit-level=high is now clean — not re-added. The prior pass's freshness, watcher-fallback, RTL-ratchet and version-derived-reference items remain open above and are not duplicated. Conclusions and sources: RESEARCH.md (2026-08-06).

P2

  • P2 — release:check:public cannot pass, because the project never signs tags Why: The public release gate rejects any unsigned tag outright — the accepted-unsigned allowlist is deliberately honoured only when checkPublic is false — while ScriptVault's stated policy is to ship unsigned. The gate is therefore unpassable by construction, which makes it as uninformative as one that always passes, and it silently failed for v3.21.0, v3.22.0 and now v3.25.0. Evidence: scripts/check-release-artifacts.mjs verifyReleaseTag() only consults legacyUnsignedTags under !checkPublic; tests/release-supply-chain.test.js:177 pins that behaviour as intentional ("allows the existing legacy unsigned tag only outside the public release gate"); npm run release:check:public fails with "git tag v3.25.0 is unsigned: error: no signature found" while every other release gate passes and verify_release_notes.py --check-published v3.25.0 succeeds. Touches: scripts/check-release-artifacts.mjs, tests/release-supply-chain.test.js, docs/release-runbook.md, Roadmap_Blocked.md (the Sigstore entry). Acceptance: a decision is recorded and enforced — either the allowlist is honoured in the public gate so an unsigned-by-policy release can pass with a visible warning, or the public gate is explicitly scoped to the checks that do not require signing and the signature assertion is removed from it. Either way npm run release:check:public returns a meaningful pass/fail for a release built under the current no-signing policy, and the test names the chosen policy rather than the current accident. Complexity: S

  • P2 — Tree-shake Monaco: the editor ships 79 language tokenizers and 5 workers to open one language Why: The bundle is imported as a full barrel, so 4.24 MB of unreachable workers and ~79 unused language tokenizers ship to every Chrome user — and this exact weight is the recorded reason Monaco is omitted from the Firefox package entirely, so pruning it is the direct unblock for Firefox editor parity rather than separate work. Evidence: src/editor/monaco-esm-entry.ts:1 import * as monaco from 'monaco-editor'; pages/editor-sandbox.html:255 and :681 are the only model-language call sites and both pass 'javascript'; built sizes lib/monaco-esm/editor.js 9.78 MB, workers/ts.worker.js 13.36 MB, css.worker.js 1.96 MB, html.worker.js 1.34 MB, json.worker.js 0.94 MB; a scan of editor.js finds 79 distinct basic-language ids; FIREFOX-PORT.md:45 "build-firefox.sh intentionally omits lib/monaco/ because AMO's linter rejects the bundled TypeScript worker as too large to parse"; monaco-editor 0.56.0 added tree-shakeable ESM entry points (https://github.com/microsoft/monaco-editor/blob/main/CHANGELOG.md). Touches: src/editor/monaco-esm-entry.ts, esbuild.config.mjs worker entry points, pages/editor-sandbox.html worker label map, scripts/check-monaco-package-contract.mjs, scripts/check-monaco-esm-prototype.mjs, docs/monaco-esm-migration-plan.md, docs/audit/monaco-esm-prototype-*.json, build-firefox.sh. Acceptance: the entry imports monaco-editor/esm/vs/editor/editor.api plus only the language contributions the product opens (javascript/typescript, and css once the UserCSS item lands); html and json workers are no longer built or mapped; the package contract budgets are lowered to the new sizes so a regression fails; npm run smoke:editor and the Monaco E2E specs still pass; the resulting bundle size is recorded against the AMO lint ceiling so FIREFOX-PORT.md can state whether Monaco now fits. Complexity: L

  • P2 — Edit UserCSS in a CSS model, not a JavaScript one Why: Persistent UserCSS was the v3.22.0 headline feature, but the editor never creates a css model, so userstyles are authored under JavaScript tokenization and receive JavaScript diagnostics — while css.worker.js is bundled and unreachable. Evidence: pages/editor-sandbox.html:255,681 both hardcode 'javascript'; no 'css' or usercss string appears in pages/editor-sandbox.html or pages/monaco-adapter.js; src/editor/monaco-esm-entry.ts:9-11 maps css/scss/less labels to a worker nothing requests; src/modules/userstyles.ts owns the UserCSS parse and tests/userstyle-compat-fixtures.test.js already exercises parseUserCSS. Touches: pages/editor-sandbox.html, pages/monaco-adapter.js, pages/dashboard.js editor-open path, src/editor/userscript-language-service.ts, tests/ editor and userstyle coverage, tests/e2e/usercss-advanced.spec.js. Acceptance: opening a userstyle selects the css language model and switching between a script and a style tab switches the model language; CSS diagnostics appear for a malformed userstyle and JavaScript diagnostics do not; @preprocessor / @-moz-document blocks do not produce spurious errors; the E2E UserCSS spec asserts the active model language. Complexity: M

  • P2 — Measure service-worker cold start and gate it Why: Nothing in the repo measures the cost users actually pay on every service-worker wake — parsing and executing a 1.81 MB script — and MV3 boot latency is the most-reported performance symptom across the whole ecosystem, so a regression here is currently invisible. Evidence: scripts/smoke-large-library.mjs and tests/large-library-perf.test.js gate MatchSet build/lookup and dashboard virtual-row render only; a grep for cold-start/boot timing across scripts/.mjs and tests/.test.js returns nothing; background.js is 44,365 lines / 1.81 MB; violentmonkey/violentmonkey#2608 ("Microfreezes in MV3 version"), Tampermonkey/tampermonkey#2347 (50-500 ms per navigation, hundreds of restarts per day), Tampermonkey/tampermonkey#2456. Touches: new scripts/smoke-service-worker-boot.mjs (mirroring smoke-large-library.mjs's --check/--json shape), package.json scripts, scripts/release-preflight.mjs, docs/large-library-perf.md, tests/ threshold mirror. Acceptance: a headless-Chromium harness forces the service worker to terminate and wake, measures boot-to-first-message-response p50/p99 over a repeated basket, writes a JSON report, and exits non-zero above a documented threshold; the harness runs against both a fresh profile and a seeded 1k-script library; the measured baseline is recorded so the production-build item can be evaluated against it. Complexity: M

  • P2 — Classify update-host failures instead of reporting them as parse errors Why: Greasy Fork is the dominant update and discovery host, and as of 2026-08-06 its API certificates had expired while update.greasyfork.org sat behind a Cloudflare challenge — an HTML challenge body reaches parseUserscript and surfaces as a generic "Parse failed", so users see a broken script rather than a broken host, and repeated challenges quietly drive the update backoff toward silence. Evidence: greasyfork-org/greasyfork#1553 (Cloudflare challenge on update.greasyfork.org breaking manager update checks), greasyfork-org/greasyfork#1561 (expired certs on api.greasyfork.org / api.sleazyfork.org); src/background/update-checker.ts:375-384 returns parsed.error ?? 'Parse failed' with no content-type or challenge classification; src/background/core.ts SubscriptionSystem and the install-interception fetch share the pattern; discovery hardcodes https://api.greasyfork.org/en/scripts.json and https://openuserjs.org/api/script/list (pages/dashboard.js); the existing exponential-backoff ring (_updateFailureCount/_updateNextCheck) treats all failures alike. Touches: src/background/update-checker.ts, src/background/core.ts (subscription plus install fetch paths), generated background.core.js/background.js, pages/dashboard.js update banner and Find Scripts source status, src/locales/en.json, tests/ update and subscription coverage. Acceptance: a response that is not a userscript is classified — host-challenge (HTML/Cloudflare interstitial), transport (TLS/DNS/network), http-status, and not-a-userscript — and each is reported with a distinct, actionable message naming the host; host-level failures do not advance the script's failure ring the way a genuine bad body does, and do not mark the script as having a pending update; the Find Scripts panel shows a per-source health state instead of an empty result list; tests cover a Cloudflare challenge body, an HTML error page, a TLS failure and a truncated body. Complexity: M

  • P2 — Prove document-start actually wins the race in a real browser Why: Unreliable @run-at document-start is one of the most-reported userscript-manager defects, and ScriptVault's native chrome.userScripts registration is believed to avoid the workarounds competitors need — but no test anywhere asserts a document-start script runs before the page's own inline script, so the claim is untested on both engines. Evidence: src/background/registration.ts:550-581,669 maps @run-at to runAt and is unit-tested for the mapping only; a grep for document-start assertions across tests/e2e/.spec.js and scripts/smoke-.mjs returns no ordering test; Tampermonkey/tampermonkey#211 and Tampermonkey/tampermonkey#2771; Violentmonkey needed an opt-in "Alternative page mode" for this and caps it at ~1 MB per page (https://github.com/violentmonkey/violentmonkey/releases/tag/v2.46.0). Touches: tests/e2e/ (new spec), tests/e2e/helpers/, scripts/smoke-firefox-sideload.mjs, README.md @run-at section, docs/cross-browser-pipeline.md. Acceptance: an E2E fixture page with an inline script that stamps a marker is visited with a registered @run-at document-start userscript; the test asserts the userscript observed the document before the page marker existed, both on a cold service worker and a warm one, and the equivalent assertion runs in the Firefox sideload smoke; the README states the measured behaviour rather than asserting it. Complexity: M

P3

  • P3 — Surface userScripts.execute() syntax diagnostics on the on-demand run path Why: Chrome 149 made userScripts.execute() validate syntax synchronously and return diagnostics on failure, and Firefox 153 added execute() at all — the on-demand run path currently reports a generic failure where the browser can now name the syntax error and its location. Evidence: https://developer.chrome.com/docs/extensions/whats-new (Chrome 149: "validates script syntax synchronously and returns diagnostics on failure"; execute() since Chrome 135); https://blog.mozilla.org/addons/2026/07/23/firefox-153-webextensions-api-updates/ ("userScripts.execute(), which provides for one-off injection"); src/background/core.ts:7327 and :9376 already branch on chrome.userScripts.execute availability with a hard error when absent. Touches: src/background/core.ts (runScriptNow / on-demand execution), generated background.core.js/background.js, pages/popup.js "Run on This Tab", pages/dashboard.js toast path, src/locales/en.json, tests/ core-flow coverage. Acceptance: when execute() rejects with diagnostics, the message shown to the user includes the reported error and line/column; when the browser supplies no diagnostics the current generic message is retained; Firefox 153+ takes the same path via feature detection and the smoke harness exercises one failing script. Complexity: S

  • P3 — Use runtime.getDocumentId() on Firefox to key per-document injection state Why: The open UserCSS SPA and injection-dedup items key state per tab, which cannot distinguish a reloaded document from a live one; Firefox 153 introduced a stable per-document identifier that makes that distinction directly. Evidence: https://blog.mozilla.org/addons/2026/07/23/firefox-153-webextensions-api-updates/ ("Firefox 153 introduces documentId, a stable identifier for a document instance, including a new runtime.getDocumentId() method"); relates to the open items "Persistent UserCSS can orphan an injected stylesheet on an SPA route change" and "SPA navigation events are dropped, not coalesced" above — this is an enabling mechanism for those, not a replacement. Touches: src/modules/userstyles.ts, src/background/core.ts (onTabNavigated/onTabUpdated/rehydrateOpenTabs), generated modules/userstyles.js, tests/userstyle-injection.test.js, scripts/smoke-firefox-sideload.mjs. Acceptance: where available, per-document injection state is keyed by documentId rather than tab id, with the tab-id path retained for Chrome; a reload no longer reuses the previous document's registry entry; the Firefox smoke harness proves a reloaded SPA page re-injects exactly once. Complexity: M

  • P3 — Review the Find Scripts surface against the 2026-08-01 Chrome Web Store prohibited-products update Why: CWS added a Malicious and Prohibited Products clause on 2026-08-01 banning circumvention of AI service safety guardrails and usage restrictions; ScriptVault's discovery panel installs arbitrary third-party scripts from Greasy Fork and OpenUserJS, and neither the listing copy nor the in-product risk copy acknowledges that class. Evidence: https://developer.chrome.com/blog/cws-policy-updates-2026 (Limited Use, Disclosure Requirements and the prohibited-products clause all effective 2026-08-01); pages/dashboard.js Find Scripts sources hardcode https://api.greasyfork.org/en/scripts.json and https://openuserjs.org/api/script/list; scripts/check-permission-copy.mjs and docs/store-listing-copy.md cover permissions and privacy but not prohibited-content classes; the @antifeature surface already exists as the disclosure mechanism (https://greasyfork.org/en/help/antifeatures). Touches: docs/store-listing-copy.md, PRIVACY.md, scripts/check-permission-copy.mjs, pages/dashboard.js Find Scripts copy, pages/install.js review copy, docs/cws-remote-code-compliance.md. Acceptance: the listing and in-product copy state that discovery surfaces third-party code the project does not author or endorse and that installation is the user's decision; the compliance doc records the 2026-08-01 clauses and how each is met; the store-copy gate covers the new clauses so a future policy change fails the check instead of passing silently. Complexity: S

  • P3 — Refresh dev dependencies and adopt the test-tooling features already paid for Why: Several dev dependencies are behind current releases and the newer versions ship capabilities that would directly improve this repo's existing harnesses; none of these are security-driven, so they belong behind the P0 audit work. Evidence: current vs latest as of 2026-08-06 — @playwright/test 1.61.1 vs 1.62.1 (2026-07-30), jsdom 29.1.1 vs 30.0.1 (2026-07-29, requires Node >=24.15 which the repo's >=24.16.0 satisfies), puppeteer-core 25.2.1 vs 25.5.0, acorn 8.17.0 vs 8.18.0, chrome-types 0.1.431 vs 0.1.436; Playwright 1.61 added page.localStorage/page.sessionStorage, 1.62 added retryStrategy 'isolated' and WebP screenshots; Vitest 4 ships Playwright trace integration usable from the existing browser-mode config; acorn 8.17's strict option and using/await-using fixes matter for the AST analyzer, whose vendored copy is already 8.17.0. Touches: package.json, package-lock.json, playwright.config.mjs, vitest.visual.config.mjs, tests/e2e/helpers/, scripts/check-cve-floors.mjs, docs/audit/cycle-22-dependency-freshness-*.md, docs/amo-vendored-libraries.md if acorn is re-vendored. Acceptance: dependencies are bumped with the full gate suite green; the extension-state assertions that currently go through page.evaluate() use the WebStorage API where it is clearer; failing browser-mode tests emit a Playwright trace; retryStrategy 'isolated' is applied to the extension-load E2E specs that have historically flaked; if acorn is re-vendored, docs/amo-vendored-libraries.md hashes are regenerated in the same change. Complexity: S