|
4 | 4 | Modern userscript manager built with Chrome Manifest V3. Tampermonkey-inspired functionality with cloud sync, auto-updates, a full dashboard, Monaco editor, DevTools panel, and a persistent side panel. |
5 | 5 |
|
6 | 6 | ## Version |
7 | | -v2.0.2 |
| 7 | +v2.0.3 |
8 | 8 |
|
9 | 9 | ## Tech Stack |
10 | 10 | - Chrome MV3 extension (JavaScript runtime + TypeScript source in `src/`) |
|
15 | 15 | - **Monaco Editor** (v0.52.2, bundled locally in `lib/monaco/`, CDN fallback in sandboxed iframe) |
16 | 16 | - Cloud sync: WebDAV, Google Drive (PKCE), Dropbox (PKCE), OneDrive (PKCE), Easy Cloud (chrome.identity) |
17 | 17 | - Vitest test suite (15 test files, 370 test cases) |
18 | | -- background.js: ~16,228 lines (built from 19+ source modules) |
| 18 | +- background.js: ~16,333 lines (built from 19+ source modules) |
19 | 19 | - 37 TypeScript source files in `src/` (type-checked via `npm run typecheck`) |
20 | 20 |
|
21 | 21 | ## Build |
@@ -477,5 +477,102 @@ All 4 bg/ modules migrated: |
477 | 477 | - Pattern builder ReDoS: 500-char length cap + bounded wildcard |
478 | 478 | - Firefox build now uses esbuild pipeline |
479 | 479 |
|
480 | | -**Version sync:** manifest.json, manifest-firefox.json, package.json all at 2.0.2 |
481 | | -- background.js: 16,228 lines |
| 480 | +### v2.0.2 Round 2 — Full Audit & Repair (2026-03-29) |
| 481 | +**Security (5 fixes):** |
| 482 | +- `GM_addElement` innerHTML XSS — sanitizes script tags, event handlers, javascript: URIs |
| 483 | +- `@connect self` was unconditional allow — now checks script @match domains |
| 484 | +- `GM_loadScript` had no @connect enforcement — now applies same rules as GM_xmlhttpRequest |
| 485 | +- Public API web install SSRF — validates HTTPS-only, rejects internal/private IPs |
| 486 | +- OneDrive OAuth missing CSRF `state` parameter — added state validation |
| 487 | + |
| 488 | +**Logic bugs (9 fixes):** |
| 489 | +- `setupAlarms` `clearAll()` wiped notification/backup alarms — now only clears autoUpdate/autoSync |
| 490 | +- `_toggleLocks` Map leaked entries — cleanup moved to `.finally()` block |
| 491 | +- `rollbackScript` left duplicate entries in version history — now removes target before push |
| 492 | +- `mergeData` returned object without tombstones — remote sync lost deletion info |
| 493 | +- `pendingInstall` left stale on non-userscript early return — now cleared |
| 494 | +- Public API `toggleScript`/`installScript` used array format — fixed for object storage format |
| 495 | +- Public API `getInstalledScripts` returned `s.name` instead of `s.meta?.name` |
| 496 | +- Notification error counts never reset after dispatch — now reset to 0 |
| 497 | +- Digest alarm skipped recreation if existing — now clears and recreates |
| 498 | +- Notification context cleanup alarms (`notifCtx_clean_*`) were silently ignored — now handled |
| 499 | +- Quota manager `userscripts` key misattributed to `other` — now in `scripts` category |
| 500 | +- Backup selective restore matched by name only — now uses `name::namespace` composite key |
| 501 | + |
| 502 | +**Race condition fixes (2):** |
| 503 | +- `_ensureOffscreen()` concurrent calls created duplicate documents — serialized via promise |
| 504 | +- EasyCloud debounce used `setTimeout` (lost on SW shutdown) — now uses `chrome.alarms` |
| 505 | + |
| 506 | +**Dashboard fixes (5):** |
| 507 | +- `dashboard-linter.js` `api.replace('.', '\\.')` only escaped first dot — now uses `/\./g` |
| 508 | +- `dashboard-csp.js` rule IDs were non-persistent counters — now uses deterministic hash |
| 509 | +- `dashboard-whatsnew.js` version mismatch caused infinite re-check — marks seen even without entry |
| 510 | +- `dashboard-debugger.js` double-init leaked interval timers — now clears on re-init |
| 511 | +- `dashboard-profiles.js` `_startUrlWatcher` could register duplicate listeners — added guard |
| 512 | + |
| 513 | +**Other fixes (4):** |
| 514 | +- Dropbox upload used stale token — now calls `getValidToken()` before upload |
| 515 | +- `content.js` bridge globals (`__ScriptVault_ChannelID__`) were writable — now `Object.defineProperty` non-writable |
| 516 | +- `bg/signing.js` regex mismatch between sign and verify — aligned to `\n?` (optional trailing newline) |
| 517 | +- `bg/analyzer.js` `_ensureOffscreen` race — serialized with cached promise |
| 518 | +- `pages/install.js` `@resource` name prototype pollution — rejects `__proto__`/`constructor`/`prototype` |
| 519 | + |
| 520 | +**Test infrastructure (2 fixes):** |
| 521 | +- Vitest worker timeouts on VMware FS — `fileParallelism: false` fixes all 15 test files |
| 522 | +- `tests/setup.js` missing mocks — added `tabs.sendMessage/get`, `action`, `webNavigation`, `cookies`, `sidePanel`, `commands`, `scripting.executeScript` |
| 523 | + |
| 524 | +**Totals:** 27 fixes across 14 files. 370/370 tests green. |
| 525 | +- background.js: 16,333 lines |
| 526 | + |
| 527 | +### v2.0.3 — Comprehensive Audit (2026-04-01) |
| 528 | +**Security (5 fixes):** |
| 529 | +- Empty `@grant` array now correctly denies all permissions (was granting ALL — critical) |
| 530 | +- `@connect` enforcement: URL parse failure in catch block now returns error instead of silently allowing request |
| 531 | +- `GM_addElement` innerHTML sanitization: case-insensitive check for `javascript:`/`vbscript:` URIs + attr names |
| 532 | +- `signing.js` `trustKey()`: rejects `__proto__`/`constructor`/`prototype` keys (prototype pollution) |
| 533 | +- Monaco adapter: postMessage listener now validates `e.source === frame.contentWindow` |
| 534 | + |
| 535 | +**Crash/logic bugs (10 fixes):** |
| 536 | +- `ScriptStorage.search()` crashed on scripts with missing `meta.name` — added null-safe access |
| 537 | +- `ScriptStorage.getByNamespace()` crashed on missing `meta` — added optional chaining |
| 538 | +- `ScriptStorage.duplicate()` crashed on missing `meta.name` — added fallback |
| 539 | +- `toggleScript` accepted `undefined` for `enabled` — now coerces to boolean or toggles |
| 540 | +- Context menu `new URL(tab.url)` crashed on chrome:// tabs — wrapped in try/catch with guard |
| 541 | +- `GM_cookie.list/set/delete` crashed on undefined result from bridge timeout — added `?.` null checks |
| 542 | +- `_makeRuleId` returned 1 for zero-hash causing DNR rule ID collisions — changed `|| 1` to `+ 1` |
| 543 | +- `requireCache.set(url, ...)` used wrong key (with SRI fragment) — now uses `fetchUrl` |
| 544 | +- Signing: Windows `\r\n` line endings caused signature verification failure — regex updated to `[^\r\n]+\r?\n?` |
| 545 | +- Signing: signature insertion failed with non-standard `==/UserScript==` whitespace — now uses regex |
| 546 | + |
| 547 | +**Performance (5 fixes):** |
| 548 | +- Backup import handlers (TM/VM/GM) called `getAll()` inside loops (O(N*M)) — cached before loop |
| 549 | +- `resources.js` O(N^2) string concatenation for binary resources — chunked `String.fromCharCode.apply` |
| 550 | +- `_urlChangeHandlers` array grew unboundedly (no dedup) — added `includes()` guard before push |
| 551 | +- `console capture` unbounded spread of `data.entries` — capped incoming to 200 before spread |
| 552 | +- `_audioWatchedTabs` Set never cleaned on tab close — added cleanup in `tabs.onRemoved` listener |
| 553 | + |
| 554 | +**Robustness (5 fixes):** |
| 555 | +- Workspaces `activate()` bypassed `ScriptStorage.set()` rollback safety — now uses `set()` per script |
| 556 | +- Background task mutex deadlock on hung sync/update — added 5-minute safety timeout |
| 557 | +- `matchIncludePattern` ReDoS via crafted glob patterns — collapse consecutive `*` before conversion |
| 558 | +- `GM_getResourceURL` defaulted to blob URLs (leak) when callers omitted arg — now defaults to data URI |
| 559 | +- Notification `_errorCounts` not persisted after reset — now saved alongside `_rateLimits` |
| 560 | + |
| 561 | +**Dashboard (7 fixes):** |
| 562 | +- `dashboard-heatmap.js`: `innerHTML +=` destroyed previously-appended DOM children — use `appendChild` |
| 563 | +- `dashboard-standalone.js`: 5 `revokeObjectURL` calls too early (100ms or sync) — all delayed to 1000ms |
| 564 | +- `dashboard-profiles.js`: `_urlWatcherStarted` not reset on `destroy()` — blocks re-init; fixed |
| 565 | +- `dashboard-chains.js`: shallow step copy let editor mutations bypass cancel — deep copy with `map(s => ({...s}))` |
| 566 | +- `dashboard-diff.js`: LCS `Uint16Array` overflows at 65535 — upgraded to `Uint32Array` |
| 567 | +- `dashboard-linter.js`: same `Uint16Array` overflow — upgraded to `Uint32Array` |
| 568 | +- `devtools-panel.js`: HAR export hardcoded version `1.7.8` — now reads from manifest |
| 569 | + |
| 570 | +**Other (4 fixes):** |
| 571 | +- Monaco adapter `'null'` origin string is truthy, `'*'` fallback never triggered for sandbox — added check |
| 572 | +- `offscreen.js` AST walker created circular `parent` refs preventing GC — now uses `_parent` + cleanup |
| 573 | +- `offscreen.js` `resolveWithMarkers` had dead `localDiff`/`remoteDiff` variables — removed |
| 574 | +- `i18n.js` regex injection via placeholder keys — now escapes special regex chars |
| 575 | +- `signing.js` extract regex aligned to handle `\r\n` consistently |
| 576 | + |
| 577 | +**Totals:** 36 fixes across 16 files. 374/374 tests green. |
| 578 | +- background.js: 16,673 lines |
0 commit comments