`JSON.parse('{"__proto__":{…}}')` produces `__proto__` as an *own
enumerable* property, so `Object.entries` yields it; and
`isPlainObject(Object.prototype)` is true, so `deepMerge` skipped its
"replace with empty object" branch and merged straight into the global
prototype. Reproduced, not inferred.
`deepRemove` had the same shape and was destructive: `"__proto__" in
target` is always true because `in` walks the prototype chain, so it
recursed into `Object.prototype` and deleted from it.
Reachable without XSS: `settings` is not in the sync skip/preserve lists,
so `common_config_*` is overwritten by whatever the WebDAV/S3 remote
sends, and opening a provider form merges it.
Guard all three walkers that share the traversal shape. The third,
`isSubset`, only reads and cannot pollute, but following
`target["__proto__"]` made `{"__proto__":{}}` a subset of *every* config,
so the "common config applied" toggle read wrong. It also now requires
own properties, since an inherited key is not "present in the config".
`isSubset` rejects on a forbidden key rather than skipping: if a future
caller bypasses sanitization, reporting "not applied" is the safe
direction because re-applying is idempotent.
That rejection alone left an inconsistency: merge skips forbidden keys
and keeps going, so `{"env":{"A":"1"},"__proto__":{}}` really did write
`env.A` while `hasCommonConfigSnippet` reported it as never applied.
Fixed by sanitizing on the *reading* side only, so the comparison runs
against exactly what the write side produces. Deliberately not applied to
the write path: `deepMerge`/`deepRemove` already skip these keys, so
sanitizing first is byte-for-byte identical there -- an unfalsifiable
call that would wrongly imply the walkers cannot handle their own input.
`deepCloneFallback` gets the same skip. Its impact differs and the
comment says so: it does not reach the global prototype, it swaps the
clone's own prototype, giving the copy ghost properties. It is dead while
`structuredClone` exists, but the two paths disagreed on `__proto__`.
* refactor: replace JSON.parse(JSON.stringify()) with structuredClone and extract useTauriEvent hook
Replace all `JSON.parse(JSON.stringify())` deep copy patterns with native
`structuredClone()` across production source (9 occurrences), tests (11
occurrences), and a hand-rolled `deepClone` utility in providerConfigUtils.ts.
Add "ES2022" to tsconfig lib for type support.
Extract a `useTauriEvent` hook to eliminate the repeated Tauri event listener
boilerplate (`useEffect` + `active/disposed` flag + async `listen`) that was
duplicated across App.tsx (3 listeners) and useUsageCacheBridge.ts. The hook
handles async registration, race-condition guards, and cleanup automatically.
* fix: add compatible deepClone helper
- Add a shared deepClone helper with a structuredClone runtime guard and fallback.
- Route clone call sites through the helper.
- Preserve universal-provider-synced listener ordering and drop the dead-directory diff.
* fix: harden Tauri event handling
- Guard WebDAV sync status events against missing payloads.
- Preserve settings query invalidation ordering before showing auto-sync errors.
- Simplify useTauriEvent subscriptions to avoid dependency-driven re-listens.
---------
Co-authored-by: zcb <zhangchongbiao@qiyuanlab.com>
Co-authored-by: Jason <farion1231@gmail.com>