Conversation
… envelope, schema validation Security - envKey/elevenlabs/script: atomic file writes (temp + rename) so a crash mid-write can't corrupt .env / hyperframes.json / script.json - safePath: realpath-resolve the deepest existing ancestor so a symlink inside <project> can't escape via realpath the loader doesn't follow - elevenlabs: strict whitelist sanitizeFilename (no leading dots, no '..', one-extension-per-component, optional expectedExt enforcement); 4500-char TTS text cap returning 413; per-project mutex on /key and /settings - planner: <user_design_brief|art_direction|research|theme_description> envelopes wrap DESIGN.md / DESIGN-ART.md / RESEARCH.md / theme.json descriptions; closing tags inside content are defanged so a hostile source file can't escape the envelope - script /theme PUT: validate the requested theme id against the registry so a typo or stale UI can't silently fall back to the default Robustness - VariantsModal/VoicesTab/ScriptTab/ProjectSwitcher: AbortController on every fetch; controllers aborted on unmount and on project switch; AbortError filtered from error UI - ScriptTab: aggregate loadError banner instead of silently-swallowed loader failures; cancel button + elapsed-seconds counter on Generate - VoicesTab: audio teardown via pause + src-clear so stale audio can't outlive a project switch; ref-tagged onended/onerror won't clobber newer state - ProjectSwitcher: client-side regex on project ids with live error - templateEngine: depth, iteration, and output-size guards (typed TemplateRenderError) so a malicious sidecar can't blow stack/memory - themes/loader: array-coerce hardening on preferences.atmospheres / transitions / icons (string-typed garbage no longer crashes the planner) - themes/registry: 1s TTL cache + invalidateThemeRegistry() so the loader doesn't re-walk docs/design-systems on every request - studio/vite.config: narrow file watcher to .ts/.tsx/.mts/.cts excluding test files; add unlink handler Validation - script /scenes/:id PUT: validate incoming.template against the project's template registry; validate incoming.props against the template's propsSchema (lightweight runtime validator, returns issues list) - ScriptTab: typed isFidelity guard replacing 'as' cast on the select Tests - new: atomicWrite, withMutex, safePath (symlink scenarios), envKey (precedence + atomicity), sanitizeFilename, wrapUserContent, templateEngine guards, validateAgainstSchema, registry caching + manifest hardening (~80 new tests, 622 core total) - core/vitest.config: testTimeout 15s for jsdom flake under parallel load - engine/ffprobe.test: skipIf the HDR fixture is a git-lfs pointer so hosts without 'git lfs' don't fail; CI Docker still runs them Verified: 1592 tests passing across 5 packages, oxlint clean, oxfmt clean, typecheck clean, Studio reloads cleanly with 12 scenes + 88 voice cards. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A wide robustness pass over the studio API and UI — security hardening, async-race fixes, schema validation, and ~80 new tests. No behavior change for the happy path; everything that previously worked still works.
Verified:
1592tests passing across 5 packages (622core,174cli,84player,221studio,491engine +3skipped on hosts withoutgit lfs).oxlintclean.oxfmtclean.tsc --noEmitclean for every package. Studio dev server reloads cleanly — ScriptTab + VoicesTab render with all 12 existing scenes + 88 voice cards, no console errors.What changed
Security
internal/atomicWrite.ts): temp + rename + chmod, parent-dir creation. Used byenvKey,elevenlabs/settings,script/theme, andscript/scenesso a crash mid-write can't leave a corrupted.env/hyperframes.json/script.json.isSafePathresolves through symlinks (studio-api/helpers/safePath.ts): walks up to the deepest existing ancestor,realpathSync, and checks the realpath is under the project realpath. Defeats in-tree symlink escapes that the previous prefix check missed.sanitizeFilename(elevenlabs.ts): per-component[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)?— no leading dots, no.., no double-extension, optionalexpectedExtenforcement so a caller can't sneak a.htmlthrough an audio path.elevenlabs.ts): 4500-char limit returning413instead of forwarding 10MB to the provider.script/planner.ts):<user_design_brief|art_direction|research|theme_description>wraps user-sourced markdown; closing tags inside content are defanged.script.ts):PUT /themerejects ids not in the registry so a typo doesn't silently fall back to the default./elevenlabs/key,/elevenlabs/settings,/theme, and/sceneswrites viawithMutexso concurrent PATCH/PUTs serialize.Robustness
VariantsModal(load,handlePick),VoicesTab(fetchVoices,refreshKeyStatus, settings, key save/clear,handleUseVoice),ScriptTab(10 controllers across all loaders + plan + generate + key save),ProjectSwitcher(loadProjects,create). All aborted on unmount and on project switch;AbortErrorfiltered from error UI.ScriptTabaggregateloadErrorbanner (R4): silent loader failures now surface a dismissable amber banner ("Couldn't reach the studio API for key status. Is the server running?") instead of leaving the UI empty.ScriptTabGenerate cancel + elapsed counter (V6): user can abort an in-flight synth without reloading.VoicesTabaudio teardown (R2):pause + removeAttribute('src') + load()releases the network/buffer; ref-taggedonended/onerrorso a stale handler can't clobber newer state after a project switch.ProjectSwitcherregex validation (V5): live error label, button disabled while invalid.templateEngineguards (R5):MAX_RENDER_DEPTH=8,MAX_RENDER_ITERATIONS=50_000,MAX_RENDER_OUTPUT_BYTES=4MB, typedTemplateRenderError.themes/loaderarray hardening (V2):asStringArrayguard sopreferences.atmospheres = "aurora"doesn't crash the planner.themes/registryTTL cache (V8): 1s TTL +invalidateThemeRegistry()so the loader doesn't re-walkdocs/design-systemson every request.studio/vite.configwatcher narrow (V7):.ts/.tsx/.mts/.ctsonly, excluding test files;unlinkhandler added.Validation
script/themes/validateProps.ts) checkstype/required/ one-level-deep properties / arrayitems.PUT /scenes/:idreturns400with anissueslist when props don't match the template'spropsSchema. Unknown template ids also rejected.isFidelityguard (V4) replaces theas "verbatim" | …" cast inScriptTab`.Tests
atomicWrite,withMutex,safePath(symlink scenarios),envKey(precedence + atomicity),sanitizeFilename,wrapUserContent,templateEngineguards,validateAgainstSchema, theme registry caching + manifest hardening.core/vitest.config:testTimeout: 15_000so jsdom rAF / animation tests don't flake under parallel load on a busy host.engine/ffprobe.test:skipIfthe HDR PNG is agit-lfspointer (host withoutgit lfs install) instead of failing with a confusing parse error. CI Docker (which fetches LFS) still runs them.Out of scope
The 4 upstream commits ahead of
origin/main(heygen-com/hyperframesv0.4.25, v0.4.26, the staged-uploadscli publishfix in PR heygen-com#491, and the sub-composition audio path fix in PR heygen-com#489) — happy to do that as a follow-up rebase if you want.Test plan
bun run --cwd packages/core test— 622 / 622bun run --cwd packages/cli test— 174 / 174bun run --cwd packages/player test— 84 / 84bun run --cwd packages/studio test— 221 / 221bun run --cwd packages/engine test— 491 / 491 + 3 skipped on hosts w/ogit lfsbunx oxlint .— 0 warnings, 0 errorsbunx oxfmt --check— cleantypecheckscripts — clean🤖 Generated with Claude Code