Skip to content

Commit 18149d1

Browse files
committed
v2.0.3: Comprehensive audit — 36 fixes across 16 files
Security: empty @grant granting all permissions, @connect bypass via malformed URL, GM_addElement case-insensitive sanitization, signing trustKey prototype pollution, Monaco adapter source validation. Crashes: ScriptStorage search/getByNamespace/duplicate null-safety, toggleScript undefined enabled, context menu tab.url crash, GM_cookie null result, _makeRuleId collision, requireCache wrong key, signing CRLF and whitespace fixes. Performance: import getAll() O(N*M) to cached, resources O(N^2) to chunked, urlchange handler dedup, console capture cap, audio tab cleanup. Dashboard: heatmap innerHTML+= DOM destruction, standalone revokeObjectURL timing, profiles urlWatcher reset, chains deep copy, diff/linter Uint32Array, devtools HAR version. 374/374 tests green. background.js 16,673 lines.
1 parent 2c42396 commit 18149d1

58 files changed

Lines changed: 12326 additions & 2366 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 101 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
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.
55

66
## Version
7-
v2.0.2
7+
v2.0.3
88

99
## Tech Stack
1010
- Chrome MV3 extension (JavaScript runtime + TypeScript source in `src/`)
@@ -15,7 +15,7 @@ v2.0.2
1515
- **Monaco Editor** (v0.52.2, bundled locally in `lib/monaco/`, CDN fallback in sandboxed iframe)
1616
- Cloud sync: WebDAV, Google Drive (PKCE), Dropbox (PKCE), OneDrive (PKCE), Easy Cloud (chrome.identity)
1717
- 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)
1919
- 37 TypeScript source files in `src/` (type-checked via `npm run typecheck`)
2020

2121
## Build
@@ -477,5 +477,102 @@ All 4 bg/ modules migrated:
477477
- Pattern builder ReDoS: 500-char length cap + bounded wildcard
478478
- Firefox build now uses esbuild pipeline
479479

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

Comments
 (0)