Skip to content

fix(lint): surface unloadable media variable defaults, stop reading data-var-src ids as paths - #3406

Merged
miguel-heygen merged 1 commit into
mainfrom
fix/var-src-unsafe-url-lint
Aug 21, 2026
Merged

miguel-heygen merged 1 commit into
mainfrom
fix/var-src-unsafe-url-lint

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

lint now errors when a composition variable's default is a media URL the runtime will
refuse to load, and it no longer mistakes a data-var-src variable id for a file path.

Closes #3368.

Why

A data-var-src value outside the runtime's scheme allowlist is dropped at bind time and
the element's authored fallback src renders instead. The video ships the wrong media and
the render exits 0. The rejection was a console.warn inside a render worker, which in a
batch is invisible.

lint could not catch it because the allowlist only existed inside
applyVariableBindings.ts. This moves that predicate into @hyperframes/parsers, where
the runtime and the linter both reach the same one, and errors on any declared default it
rejects. The value provably cannot load, so the rule has no false-positive case.

Rejecting file: stays correct — the allowlist is a security boundary and is unchanged.
The bug was the silence, not the policy.

A second defect found while reproducing it

Linting the reporter's repro produced an error about a missing file named bg, which is
the variable id, not a file anyone referenced:

✗ missing_local_asset: <img> element references local file(s) not found in the project: bg.

Three rules shared this regex:

/<(video|img|source)\b[^>]*\bsrc\s*=\s*["']([^"']+)["'][^>]*>/gi

\bsrc\s*= also matches the tail of data-var-src="bg" — hyphen to s is a word boundary
— and [^>]* is greedy, so the variable id beat the real src earlier in the same tag.
Every element using a variable binding was reported as referencing a missing asset named
after its variable, and <audio data-var-src> was additionally told the render would be
silent. All three now share one helper that requires whitespace before the attribute.

How

  • isSafeMediaUrl moves from packages/core/src/runtime/applyVariableBindings.ts to
    packages/parsers/src/compositionVariables.ts; the runtime imports it rather than owning
    a copy, so lint and the runtime cannot drift.
  • New unloadable_media_variable_default error in packages/lint/src/rules/composition.ts,
    applied to image-typed variables and to any id an element binds with data-var-src.
  • mediaSrcTagRe() in packages/lint/src/utils.ts replaces the three copies of the src
    regex in project.ts (×2) and hevcPreviewLint.ts.

Test plan

Reporter's repro, an <img> with a red fallback src bound to a variable defaulting to an
absolute file:// path to a blue image.

Before ([email protected]) — reports a file that was never referenced, says nothing
about the binding:

✗ missing_timeline_registry: Missing `window.__timelines` registration.
⚠ missing_data_no_timeline: ...
✗ missing_local_asset: <img> element references local file(s) not found in the project: bg.
◇  2 error(s), 1 warning(s)

After (this branch) — the phantom file is gone, the real defect is named:

✗ missing_timeline_registry: Missing `window.__timelines` registration.
⚠ missing_data_no_timeline: ...
✗ unloadable_media_variable_default: Variable "bg" defaults to a URL the runtime will
  refuse to load, so any element bound to it renders its authored fallback src instead
  and the render still exits 0.
  Fix: Media URLs must be relative, http(s), blob:, or a data:image/* URI. Copy the file
  into the project and reference it relatively (e.g. "assets/bg.png") rather than by
  absolute path.
◇  2 error(s), 1 warning(s)

Suites, each with its own package config:

package result
packages/core 2491 passed (122 files)
packages/parsers 922 passed, 4 skipped, 3 todo
packages/lint 515 passed

Six new tests: four on the new rule (file:// on an image variable, file:// on a
non-image variable a data-var-src consumes, relative/http/data:image staying quiet, an
unbound scalar not being treated as a URL) and two on the regex (a real src reported
instead of the variable id, no invented asset when the src is a templating token).

Not covered

  • --variables / --variables-file values are still only checked at bind time. Lint sees
    declared defaults, not run-time overrides, so passing a rejected URL through the flag
    keeps the original silent-fallback behaviour. Fixing that belongs at the CLI boundary and
    is a separate change.
  • The runtime still warns and continues rather than failing the render. That matches the
    framework's existing policy for media that cannot load (a missing src renders a broken
    image and exits 0, frameCapture.ts:387); changing it is a policy decision, not a bug
    fix, and would need its own discussion.

…ata-var-src ids as paths

A data-var-src value the runtime refuses to load is dropped at bind time and
the element's authored fallback src renders instead, so the video ships the
wrong media and the render still exits 0. lint said nothing, because the
scheme allowlist only existed inside the runtime.

Move that predicate into @hyperframes/parsers, where both the runtime and the
linter can reach it, and error at lint time on any declared default it
rejects. The value provably cannot load, so there is no false positive.

While reproducing that, lint turned out to report an unrelated missing file:
`\bsrc\s*=` also matches the tail of `data-var-src="bg"` (hyphen to `s` is a
word boundary), and `[^>]*` is greedy, so the variable id beat the real src
earlier in the same tag. Every binding was reported as a missing asset named
after the variable, and `<audio data-var-src>` was told the render would be
silent. All three copies of that regex now share one helper that requires
whitespace before the attribute.
@miguel-heygen
miguel-heygen force-pushed the fix/var-src-unsafe-url-lint branch from 8004aab to ec52f49 Compare August 21, 2026 23:01
@miguel-heygen
miguel-heygen merged commit 41edbfb into main Aug 21, 2026
58 checks passed
@miguel-heygen
miguel-heygen deleted the fix/var-src-unsafe-url-lint branch August 21, 2026 23:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

data-var-src silently renders the authored fallback when the bound URL is rejected

1 participant