Surfaced by a post-merge review of the programmable GLSL shader background shipped in #11083. The shipped paths are safe (the BACKGROUND agent action emits only a presetId/uniform patch — never GLSL text; the settings UI has no GLSL editor; presets use bounded ≤5-iteration loops; and there's real defense-in-depth: a static gate, an authoritative GL compile-validate before display, a frame-time watchdog, WebGL context-loss handling, and a CSS-color fallback). These are latent / hygiene follow-ups, not shipped-path exploits.
1. Latent raw-GLSL sink not fully bounded (defense-in-depth)
packages/ui/src/backgrounds/useBackgroundApplyChannel.ts accepts a raw source field from any background:apply broadcaster (the explicitSource branch). The static gate isPlausibleFragmentSource (packages/ui/src/backgrounds/shader-schema.ts) bans while/do and caps size at 16KB, but does not bound for-loop iteration counts — a crafted for(int i=0;i<200000;i++){...} passes the gate and the GL compile-validate, and can stall the GPU for one very long frame. The frame-time watchdog (ProgrammableShaderBackground.tsx:213-217) is best-effort: it only evaluates between rAF callbacks and needs 5 consecutive >120ms frames, so a single pathological frame can freeze the tab before the next callback runs.
No shipped caller sends GLSL text today, so this is only reachable if a future caller ships raw source through background:apply. Fix options: (a) drop the explicitSource branch entirely (presets are the only intended source), or (b) add a for-loop iteration-bound heuristic to isPlausibleFragmentSource.
2. Uniform-only tweaks rebuild the entire WebGL context (perf)
packages/ui/src/backgrounds/ProgrammableShaderBackground.tsx:227 — the single useEffect has deps [source, uniforms, color], so a uniform-only tweak (e.g. a "make it slower" glsl-tweak that keeps the same source) changes the uniforms object identity and tears down + rebuilds the whole THREE.WebGLRenderer + WebGL context + recompiles the shader instead of mutating uniformDefs.u_*.value. Rapid tweaks churn WebGL contexts (browsers cap ~16 live contexts → oldest force-killed) and add GC pressure. Fix: mutate live uniform values (and renderer size/color) in a lightweight effect keyed on the changed values; reserve the heavy build/compile path for actual source changes.
Minor (nits, from the same review)
- rAF render loop has no explicit Page Visibility / IntersectionObserver pause (relies on the browser suspending rAF for hidden tabs; matters for an always-on occluded desktop/Electron window).
ProgrammableShaderBackground.tsx:225.
- On WebGL context loss,
onContextRestored calls onFallback → permanent swap to the CSS color field until the background config changes; a user may expect the animated shader to resume after a transient GPU reset.
Evidence: multi-dimension review of #11083 at merge commit a9be4f4.
Surfaced by a post-merge review of the programmable GLSL shader background shipped in #11083. The shipped paths are safe (the BACKGROUND agent action emits only a presetId/uniform patch — never GLSL text; the settings UI has no GLSL editor; presets use bounded ≤5-iteration loops; and there's real defense-in-depth: a static gate, an authoritative GL compile-validate before display, a frame-time watchdog, WebGL context-loss handling, and a CSS-color fallback). These are latent / hygiene follow-ups, not shipped-path exploits.
1. Latent raw-GLSL sink not fully bounded (defense-in-depth)
packages/ui/src/backgrounds/useBackgroundApplyChannel.tsaccepts a rawsourcefield from anybackground:applybroadcaster (theexplicitSourcebranch). The static gateisPlausibleFragmentSource(packages/ui/src/backgrounds/shader-schema.ts) banswhile/doand caps size at 16KB, but does not boundfor-loop iteration counts — a craftedfor(int i=0;i<200000;i++){...}passes the gate and the GL compile-validate, and can stall the GPU for one very long frame. The frame-time watchdog (ProgrammableShaderBackground.tsx:213-217) is best-effort: it only evaluates between rAF callbacks and needs 5 consecutive >120ms frames, so a single pathological frame can freeze the tab before the next callback runs.No shipped caller sends GLSL text today, so this is only reachable if a future caller ships raw source through
background:apply. Fix options: (a) drop theexplicitSourcebranch entirely (presets are the only intended source), or (b) add a for-loop iteration-bound heuristic toisPlausibleFragmentSource.2. Uniform-only tweaks rebuild the entire WebGL context (perf)
packages/ui/src/backgrounds/ProgrammableShaderBackground.tsx:227— the singleuseEffecthas deps[source, uniforms, color], so a uniform-only tweak (e.g. a "make it slower" glsl-tweak that keeps the same source) changes theuniformsobject identity and tears down + rebuilds the wholeTHREE.WebGLRenderer+ WebGL context + recompiles the shader instead of mutatinguniformDefs.u_*.value. Rapid tweaks churn WebGL contexts (browsers cap ~16 live contexts → oldest force-killed) and add GC pressure. Fix: mutate live uniform values (and renderer size/color) in a lightweight effect keyed on the changed values; reserve the heavy build/compile path for actual source changes.Minor (nits, from the same review)
ProgrammableShaderBackground.tsx:225.onContextRestoredcallsonFallback→ permanent swap to the CSS color field until the background config changes; a user may expect the animated shader to resume after a transient GPU reset.Evidence: multi-dimension review of #11083 at merge commit a9be4f4.