Remotion Under the Microscope: 1.25M Lines, 72 Packages, Nine Licence Strings
Remotion is the incumbent of programmatic video and its licence is its business model. We indexed all 1,253,947 lines at commit 217e07f and ran a full static audit: how a React state update becomes a video frame, why zero dependency cycles after five years is the most impressive number in the repo, the six packages published as UNLICENSED, and the prebuilt encode binaries that self-report GPL and contain the one codec FFmpeg refuses to redistribute.
Key Takeaways
Remotion renders React to video by driving a forked headless Chrome and a Rust compositor. A full static audit at commit 217e07f (v4.0.503) found a genuinely mature codebase — zero function and file cycles across 1.25M lines and 124 packages, modularity Q=0.590, only two articulation points, no proven vulnerability and no critical or high tech-debt hotspots — alongside five validated findings: 72 publishable packages carrying nine different licence strings, a publish script that ships licence text to only 19 of them, six packages published as SPDX UNLICENSED including two runtime dependencies of the Lambda renderer, prebuilt compositor binaries that self-report GPL-2.0-or-later and bundle the Fraunhofer FDK-AAC encoder that the default render path selects, and a bus factor of 1.
On 23 June 2020, a Swiss developer named Jonny Burger made the initial commit to a repository that proposed something most of the industry considered a curiosity: write video the way you write a website. Six years later that repository holds 34,729 commits, 897 release tags, 441 contributors and 1,253,947 indexed lines of code, and the idea is no longer a curiosity. Remotion is the default answer when a team needs to generate video from data.
It is also a business. Remotion is source-available rather than open source: individuals, non-profits and companies with up to three employees use it free; everyone else buys a company licence. That single document is the commercial engine of the entire project, which makes the state of its published licence metadata the most consequential thing this audit found — and not in the direction anyone would want.
We took the repository at commit 217e07f (v4.0.503, 31 July 2026) and put it through a full static audit with Code Indexer, a semantic code-search and analysis engine that builds a queryable graph of a codebase rather than reading files one at a time. The index holds 61,239 symbols and 266,826 references, resolved into 108,964 static call edges. Every number below was computed from that graph or measured directly; every finding was reproduced by hand before it was written down. Where the tooling was wrong, there is a section that says so.
Part I — How a React state update becomes a video frame
Time is not frozen. The page is asked whether it is ready.
Rendering video from a browser means solving one problem: a browser is the least deterministic environment in mainstream computing, and video demands that frame 1,847 look identical on every machine, every run. There are two ways to solve it. You can abolish the browser's notion of time — replace Date, performance.now and requestAnimationFrame with a virtual clock you control. Or you can leave time alone and make the page tell you when it has finished settling. Remotion takes the second path, and the whole architecture follows from that choice.
The mechanism lives in thirty-five lines. packages/renderer/src/seek-to-frame.ts waits for the page to report readiness, evaluates window.remotion_setFrame(frame, composition, attempt) inside the page, waits for readiness again, and then awaits document.fonts.ready before photographing anything. The interesting half is on the browser side. packages/core/src/TimelineContext.tsx installs that global, and what it does is subtle: it opens a delayRender() handle labelled 'Setting the current frame to N', pushes the frame into React state, and calls continueRender() only from inside a requestAnimationFrame callback — that is, after React has actually committed the update. If the frame has not changed it short-circuits synchronously rather than burning a cycle.
delayRender() is not an internal trick; it is the public API. Any component that loads a font, fetches data or decodes an image opens a handle and closes it when finished, and the renderer simply will not advance until the count reaches zero. The default timeout is 30,000 milliseconds, and when it expires the renderer does something genuinely thoughtful: it evaluates window.remotion_delayRenderTimeouts inside the page and reports which labelled handles are still open. The resulting error — 'Open delayRender() handles: 1. Loading font, 2. Fetching chart data' — is the best piece of developer experience in either of the two codebases we have audited in this series.
Correctness is a contract with the composition author, not a property of the sandbox. A component that animates off Date.now() will render wrong, and nothing in the framework will stop it.
The renderer-to-page contract is 45 mutable globals
packages/core/src/index.ts contains a single interface Window declaration with 45 remotion_* members — remotion_renderReady, remotion_setFrame, remotion_delayRenderTimeouts, remotion_cancelledError, remotion_inputProps, remotion_envVariables, remotion_mediaCacheSizeInBytes, and forty more. Repo-wide, 94 distinct remotion_* identifiers are referenced. That flat namespace is the entire API between the Node renderer and the browser bundle, shared by the renderer, the Studio, the Player and the browser-side web renderer. It is not a versioned message channel and not a typed RPC. It is global mutable state, and it has worked for five years.
It is also why one file shows up as a structural single point of failure. Articulation-point analysis over all 108,964 call edges returns exactly two cut vertices for the whole repository. One is an artefact — packages/google-fonts/src/base.ts, imported by 1,849 generated font modules. The other is packages/core/src/delay-render.ts, degree 60, separating 1,852 modules and 264,016 lines of code. That is not a defect. It is a precise statement of where the architecture's weight rests, and it is the kind of thing that only falls out of a whole-graph analysis — no file-by-file review surfaces it.
The stack, in full
Remotion is often described as 'React for video', which undersells it by roughly a hundred packages. The monorepo holds 124 package directories, 72 of them publishable, written in 22 languages of which 15 are code. TypeScript accounts for 64% of files and TSX another 27%, but the parts that matter most are the ones that are not TypeScript at all.
| Layer | What is actually in the repository |
|---|---|
| Languages | TypeScript 64%, TSX 27%, plus Rust, Go, PHP, Python, Ruby, C/WASM — 22 languages, 15 of them code |
| Build tooling | Bun as package manager and test runner, Turborepo for orchestration, oxfmt, and an in-house @remotion/eslint-plugin |
| Bundlers | webpack AND rspack AND esbuild, with Babel and SWC transform paths |
| Browser control | A vendored fork of Puppeteer — packages/renderer/src/browser, 41 files and 7,952 lines. @remotion/renderer has no puppeteer dependency at all |
| Native encode | A Rust binary (crate remotion-renderer, 25 files / 4,494 lines) depending on ffmpeg-next and mp4-rust, both maintained forks, plus rayon-core and sysinfo |
| Media parsing | @remotion/media-parser — 459 files and 54,189 lines of pure TypeScript demuxing across ten container families: ISO-BMFF, WebM, RIFF/WAV, MP3, AAC, FLAC, AVC, M3U/HLS, MPEG-TS |
| Serverless | @remotion/serverless as a provider-agnostic core, with AWS Lambda and GCP Cloud Run providers and thin clients in Go (1,199 lines), PHP (3,552), Python (2,207) and Ruby (439) |
| Editor UI | React 19, Radix UI, Tailwind, Monaco, and recast-based codemods that write timeline edits back into your .tsx source |
| Testing | Bun test and Vitest including browser mode, Playwright, plus dedicated SSR, template, Lambda, visual-effects and browser-Studio suites across 53 packages |
| Agent surface | @remotion/mcp (a Model Context Protocol server), 11 skills totalling 6,831 lines of Markdown, and dedicated Claude Code, Codex and Kimi plugins |
| Metering | @remotion/licensing posts usage events to remotion.pro — but only when a licence key is present |
Two details deserve more than a table row. The first is that the Studio writes code. packages/studio-server/src/codemods/ and packages/studio-codemods/ use recast to apply timeline edits — keyframes, default props, sequence bounds — back into the user's React source. Drag a clip in the visual editor, get a diff in your repository. It is the hardest thing in the product to build and the easiest to underestimate.
The second is that @remotion/media-parser is quietly enormous. Fifty-four thousand lines of pure TypeScript that demux MP4, WebM, HLS and transport streams without touching FFmpeg — which is what makes browser-side rendering possible at all, since a browser cannot shell out to a binary. Personalised PageRank over the call graph confirms how central it has become: the three most depended-on symbols in the entire repository all live in media-parser, led by a map in audio-sample-map.ts with 1,386 inbound references. The newest large subsystem is already the graph's centre of gravity.
The metering deserves a precise description, because it differs by render path. packages/licensing/src/register-usage-event.ts posts to remotion.pro with three retries and exponential backoff. On the server and Lambda paths it is key-gated: packages/serverless/src/handlers/send-telemetry-event.ts returns immediately when the licence key is null, so no key means no call. The browser path is different. packages/web-renderer/src/send-telemetry-event.ts guards only on whether it is running in a browser at all; a missing licence key produces a console warning and the event is sent anyway, and passing the sentinel 'free-license' does not suppress it either — it merely nulls the key field. The payload carries window.location.origin, the host page's domain. So: key-gated metering on the server, unconditional reporting from the in-browser renderer.
Part II — The audit
A conventional LLM code review reads files. At 1.25 million lines that is not a strategy, it is a sampling exercise. Code Indexer embeds and indexes every chunk, resolves the symbol graph, and runs graph algorithms over it — Personalised PageRank for load-bearing symbols, Louvain community detection for architectural seams and cut vertices, and a git-history join for ownership and defect concentration.
The first result that came back is the one worth leading with. Across 1,253,947 lines, 124 packages and five years of continuous change, the partition has 4,146 communities at modularity Q = 0.590, and cycle detection found zero function cycles and zero file cycles. Not few. Zero. Anyone who has watched a large TypeScript monorepo age will recognise how unusual that is; import cycles are the entropy that accumulates by default. The tech-debt sweep agrees: zero critical, zero high and zero medium risk hotspots. This is a codebase that has been curated, not merely grown.
The exposures are elsewhere — and none of them are in the renderer.
| # | Finding | Location | Severity | How it was verified |
|---|---|---|---|---|
| 1 | 72 publishable packages carry nine distinct licence strings; 53 ship no licence text | publish.ts:60-66 and every packages/*/package.json | High (adoption) | Live npm registry queried at v4.0.503; tarballs downloaded and listed |
| 2 | Prebuilt compositor binaries self-report GPL-2.0-or-later and bundle FDK-AAC, inside packages with no licence | @remotion/compositor-* v4.0.503 | High (legal) | Published binaries executed: ffmpeg -L, -encoders, strings, ldd |
| 3 | The default MP4 render path selects the FDK-AAC encoder | packages/renderer/src/options/audio-codec.tsx:44-46, 89-92 | Medium | Source read; default codec chain traced end to end |
| 4 | Bus factor 1 — 82% of six-month commits, 81% of file ownership | git history | High (key personnel) | git shortlog plus per-file primary-ownership fold |
| 5 | Renderer-to-page contract is 45 mutable globals; delay-render.ts is a graph cut vertex | packages/core/src/index.ts:49-109 | Medium | Interface counted; articulation analysis over 108,964 edges |
Finding 1 — nine licence strings for one licence
Remotion's LICENSE.md is a careful, readable document. A Free Licence covers individuals, non-profits, evaluators and for-profit organisations with up to three employees. Everyone else needs a paid Company Licence. The root package.json declares 'SEE LICENSE IN LICENSE.md'. That is coherent, and it is the foundation the company's revenue sits on.
The published metadata does not match it. Across the 72 publishable manifests there are nine distinct values in the licence field.
We checked the live npm registry rather than trusting the repository, in case the publish pipeline normalises the field. It does not. At version 4.0.503, remotion, @remotion/renderer, @remotion/player, @remotion/cli, @remotion/bundler and @remotion/lambda all publish with 'SEE LICENSE IN LICENSE.md'. @remotion/studio, @remotion/studio-server, @remotion/captions, @remotion/paths and @remotion/shapes publish as MIT. Six packages publish as UNLICENSED. Thirteen publish with no licence field at all, including @remotion/media, @remotion/cloudrun and all seven @remotion/compositor-* platform binaries.
UNLICENSED is not a neutral placeholder. In npm's own semantics it means proprietary, no rights granted, and Snyk, FOSSA, Black Duck and GitHub's dependency review all resolve it to a hard block. Two of the six — @remotion/lambda-client and @remotion/serverless — are runtime dependencies of @remotion/lambda, so an organisation adopting Lambda rendering pulls them in transitively without ever naming them. @remotion/transitions, another of the six, is among the most widely installed packages in the ecosystem.
| Package | Published licence field at v4.0.503 | Licence text in tarball |
|---|---|---|
| remotion | SEE LICENSE IN LICENSE.md | Yes |
| @remotion/renderer | SEE LICENSE IN LICENSE.md | Yes |
| @remotion/studio | MIT | No |
| @remotion/transitions | UNLICENSED | No |
| @remotion/effects | UNLICENSED | No |
| @remotion/serverless | UNLICENSED | No |
| @remotion/lambda-client | UNLICENSED | No |
| @remotion/whisper-web | UNLICENSED | No |
| @remotion/media | no licence field | No |
| @remotion/cloudrun | no licence field | No |
| @remotion/compositor-linux-x64-gnu | no licence field | No |
| @remotion/media-parser | Remotion License + URL | No |
The mechanism turns out to be a single line, and it is the kind of thing that is invisible until you look for it. In publish.ts, the root licence text is copied into a package's tarball only if that package's own licence field contains the substring 'LICENSE.md'. Thirteen packages match; six more carry their own committed licence file. That is 19 of 72. The remaining 53 publish with no licence text in the tarball at all — every UNLICENSED one, every MIT one, and @remotion/media-parser, whose field points at a URL that the substring test does not match.
We confirmed it empirically rather than by reading the code alone. The remotion and @remotion/paths tarballs contain package/LICENSE.md. The tarballs for @remotion/transitions, @remotion/effects, @remotion/serverless, @remotion/lambda-client, @remotion/studio, @remotion/media, @remotion/media-parser and @remotion/webcodecs contain no licence file of any kind. For a company whose revenue depends on people reading the licence, widening that condition is the highest-return change available in the repository, and it is roughly an afternoon of work.
Finding 2 — the encode binaries are GPL, and contain the one codec FFmpeg will not redistribute
This is the finding we did not expect to make, and it required leaving the repository entirely. @remotion/compositor-linux-x64-gnu at version 4.0.503 is a 10.3 MB tarball containing ffmpeg, ffprobe, the Rust remotion binary and seven FFmpeg shared libraries. Because the Linux build runs on Linux, you can simply ask it what it is.
It answers with its own build configuration: --enable-gpl --enable-libx264 --enable-libx265 --enable-libfdk-aac --enable-libmp3lame --enable-libopus, on FFmpeg n7.1. Four consequences follow, each independently checkable by anyone with npm pack and five minutes.
- All seven shipped libraries self-report GPL. Running strings against libavcodec.so returns the literal line 'libavcodec license: GPL version 2 or later', and the same is true of libavutil, libavformat, libavfilter, libavdevice, libswscale and libswresample. This follows directly from --enable-gpl combined with libx264 and libx265, both GPL-2.0-or-later.
- FDK-AAC is compiled in and live. ffmpeg -encoders lists libfdk_aac as an available AAC encoder, and the library carries 43 Fraunhofer symbols.
- The nonfree marker is absent. No 'nonfree' string appears anywhere in the shipped libraries, and the licence string reads 'GPL version 2 or later' rather than 'nonfree and unredistributable'. FFmpeg's own build system normally refuses --enable-libfdk-aac unless --enable-nonfree is also passed, because the Fraunhofer licence is treated upstream as GPL-incompatible, and builds that do enable it are explicitly marked as unredistributable.
- The Rust compositor dynamically links all seven. ldd on the remotion binary resolves every libav and libsw library from the same package directory, which makes them a linked dependency of the shipped executable rather than an arm's-length subprocess.
The macOS ARM64 package is identical in configuration — same GPL flags, same FDK-AAC, same self-reported licence, and likewise no licence file in the tarball. So this is not a one-platform packaging accident; it is the build recipe.
GPL-2.0 attaches source-availability and notice obligations to binary distribution, and the distributor here is Remotion rather than the user. The npm packages carrying these binaries declare no licence field and ship no licence text at all. We state this as a question for the vendor rather than a proven violation: the FFmpeg build recipe is not in this repository, Remotion may hold a Fraunhofer agreement we cannot see, and none of this is legal advice. But any competent software-composition-analysis pass will surface it, and enterprise procurement will stop on it before an engineer ever notices.
Finding 3 — the default render selects that encoder
This is what turns the previous finding from packaging trivia into a decision every Remotion user makes without knowing it. In packages/renderer/src/options/audio-codec.tsx, mapAudioCodecToFfmpegAudioCodecName returns 'libfdk_aac' whenever the requested audio codec is 'aac'. Forty-five lines further down, the defaults table sets h264 to compressed 'aac'. h264 is the default video codec, and compressed is the default audio mode.
So a plain npx remotion render produces an MP4 whose audio was encoded by Fraunhofer FDK-AAC. Remotion's binary also ships FFmpeg's own native aac encoder, which is compiled in and quality-competitive at modern bitrates. Changing one return statement would resolve the entire question, at the cost of a small quality delta at low bitrates. That is a genuinely cheap fix for a genuinely expensive category of risk.
Finding 4 — the bus factor is 1
Of the 4,941 commits in the last six months, 4,056 — 82.1% — are authored by Jonny Burger across two Git identities. Per-file primary ownership, folded from git log, gives him 3,787 of 4,664 analysed files, or 81%. The second-place contributor owns 433 files across 305 commits. Losing one person would orphan more than half the repository.
The knowledge islands — files with a single author and active churn — are not peripheral utilities. They are the Studio timeline (TimelineExpandedSection.tsx at 55 commits, TimelineFieldRow.tsx at 54, TimelineSelection.tsx at 47) and the core schema layer (use-schema.ts at 44, wrap-in-schema.ts at 31). That is the interactive-editing surface, which is the product's newest strategic bet and the part a competitor would find hardest to copy.
Context is owed here. Four hundred and forty-one people have committed over five years and 89 in the last six months, which is an unusually healthy contributor funnel for a source-available product; the repository is visibly welcoming to outside patches. But the concentration has not diluted as the project has grown, and a bus factor of 1 on a codebase this large is the most serious organisational finding in this report. For comparison, HyperFrames — five months old, 57 contributors — scores 2.
Part III — What the audit got wrong
An audit that reports only its hits is marketing. Four tool signals were rejected or materially adjusted, and one of the rejections turned into a compliment.
The divergent-clone shortlist is generated code — and the one real clone is already guarded. The top three refactor candidates came back as packages/google-fonts/src/KiwiMaru.ts, MPLUS1Code.ts and RocknRollOne.ts, flagged as near-duplicates diverging at a hex literal. They are three of 1,852 machine-generated per-font modules. Pure false positive. But checking by hand turned up something better: packages/cli/src/list-of-remotion-packages.ts and packages/create-video/src/list-of-remotion-packages.ts are byte-identical 104-line files in two different packages with no import between them — precisely the sibling-drift pattern that produced the most serious finding in our HyperFrames audit. Here it is machine-guarded. packages/it-tests/src/monorepo/package-sync.test.ts regenerates both files from the canonical package list in @remotion/studio-shared and asserts they are equal. Remotion anticipated the exact failure class that bit its competitor.
Documentation coverage of 2% is a category error. The audit measured 94 of 4,938 functions carrying docstrings, which lands at the 5th percentile against peers. Remotion also ships over a thousand pages of prose documentation, 1,327 indexed documentation chunks, 11 agent skills and an MCP server whose entire purpose is serving that documentation to coding agents. Docstring density is not documentation quality. The honest statement is that Remotion documents its product exceptionally well and its internals barely at all — a defensible allocation for a source-available project with one dominant maintainer, and a real onboarding cost for contributors.
The 11% test ratio understates the suite. That metric counts 1,852 generated Google-Fonts modules as source files. Excluding generated, documentation, template and example trees, the ratio is 815 test files against 4,774 source files — 17%. More importantly, 53 packages carry test scripts spanning unit, server-side-rendering, template-integration, Lambda, end-to-end Playwright, visual-effects and browser-Studio suites, none of which a file-count ratio can see. Remotion's testing is genuinely thinner than HyperFrames' 63% and 55,283 assertions, but it is 17% rather than 11%, and the strategy is integration-weighted rather than unit-weighted.
The 230 security findings are not vulnerabilities. Confidence tiering resolves all 230 as hypothesis-tier — zero proven, zero likely, every one a static pattern match with no data-flow corroboration. The engine floors the security category and labels it as such. The dependency scan is a longer story: it initially reported zero CVEs across 222 resolved dependencies while running with an empty advisory cache, which means 'not checked' rather than 'clean'. We refreshed the cache and re-ran it, and the result is in the update at the end of this article — including a coverage gap larger than the findings it produced.
Part IV — Quality profile
Remotion scores 43 out of 100 on a scale calibrated for enterprise production readiness, which places it at the 46th percentile against a corpus of 64 audited open-source developer tools whose median is 44. Read the percentile, not the letter: mature open-source projects routinely land in the C-to-F band on this scale, and the categories are far more informative than the aggregate.
Where Remotion is genuinely top-tier: maintainability at the 75th percentile, dead-code share and complex-function share both in the favourable tail, magic numbers at 0.0 per thousand lines, and modularity of 0.590 with zero cycles of any kind across a 1.25-million-line, 124-package monorepo that has been shipping continuously since 2020. That last number is the one to be impressed by. Cycle-free at this scale and this age is rare and does not happen by accident — it requires someone saying no to convenient imports, thousands of times, over five years.
The weakest categories are honest ones. Architecture scores zero not because of cycles but because of 20 coupling hotspots, 20 low-cohesion files and the two cut vertices already discussed. Documentation scores 2 of 10 for the reason set out above. Test coverage at 8 of 15 reflects a real gap relative to the best-in-class, even after adjusting the ratio upward.
Part V — Remotion versus HyperFrames, measured
We audited HeyGen's HyperFrames with the same tooling last week, which makes a like-for-like comparison possible for the first time. Neither project is a strict superset of the other, and the numbers below are the ones that survive scrutiny.
| Remotion (217e07f) | HyperFrames (89e970f) | |
|---|---|---|
| Age at audit | 5 years 1 month | 4 months 3 weeks |
| Indexed lines | 1,253,947 | 823,426 |
| Symbols / references | 61,239 / 266,826 | 69,869 / 312,603 |
| Call edges / communities / modularity | 108,964 / 4,146 / 0.590 | 96,369 / 4,048 / 0.534 |
| Function and file cycles | 0 / 0 | 0 / 0 |
| Authoring model | React components | Plain HTML with data-* attributes |
| Determinism strategy | Readiness protocol (delayRender) | Virtual-time shim (freeze the clock) |
| Encode path | Rust compositor + bundled custom FFmpeg | System-installed FFmpeg |
| Agent surface | MCP server + 11 skills / 6,831 lines | 19 skills / 39,534 lines |
| Test-to-source ratio | 17% (adjusted) | 63% |
| Bus factor | 1 | 2 |
| Licence | Source-available, paid above 3 employees | Apache-2.0 |
| Licence metadata consistency | 9 strings; 53 of 72 tarballs carry no licence text | 1 of 14 manifests declared a licence |
| Peer percentile | 46th | 93rd |
Remotion is a five-year-old platform with four render surfaces, four language clients, a from-scratch TypeScript demuxer and an editor that rewrites your source code. HyperFrames is a four-month-old renderer with markedly better test discipline, a narrower thesis and a permissive licence. The rows that decide procurement arguments are the last three — and it is striking that both projects, independently, ship packages whose licence metadata contradicts their own licensing intent.
Verdict
Remotion is a serious, long-lived engineering artefact, and the letter grade is the least informative thing in this report. Zero cycles across 1.25 million lines and five years, two articulation points in a 108,964-edge graph, no proven vulnerability, no critical or high tech-debt hotspot, and a from-scratch media parser that has already become the codebase's centre of gravity. The renderer works, the architecture holds, and the parts that look alarming in the aggregate score resolve on inspection into deliberate trade-offs.
Its real exposures sit outside the code that renders video. The licence metadata contradicts the licence in nine different ways, and 53 of 72 published tarballs ship with no licence text at all — for a source-available business, the highest-return fix available and among the cheapest. The prebuilt encode binaries self-report GPL-2.0-or-later and bundle a codec that FFmpeg upstream marks unredistributable, which the default render path then selects, inside npm packages that declare no licence. And the bus factor is 1, concentrated in the newest and most strategically important surface.
For teams evaluating it: the technology is production-grade and has five years of evidence behind it, which is more than any competitor can currently claim. Budget real time for the licence conversation — not because Remotion's licence is unclear, since it is unusually clear, but because its published metadata is, and because the encode binaries carry terms your scanner will flag long before your engineer does. Both are fixable in a week. Neither is fixed today.
Methodology. Every figure in this article was computed by Code Indexer over the repository at commit 217e07f — 61,239 symbols, 266,826 references, 108,964 call edges, 4,146 communities — or measured directly with git. Findings 1 through 5 were each reproduced by hand: manifests enumerated across 124 package directories, the live npm registry queried at v4.0.503, tarballs downloaded and listed, the published FFmpeg binaries executed to report their own build configuration and encoder list, ldd run for linkage, source line ranges opened, and every libfdk_aac call site traced. Four tool signals were rejected or adjusted and are documented above. Runtime behaviour of the framework itself is outside the boundary of this analysis, and nothing here is legal advice.
Update — the dependency scan, re-run properly
After publication we refreshed the advisory cache — 276,882 OSV entries across npm, PyPI, crates.io, Maven, Packagist, Go and RubyGems — and re-ran the dependency scan against Remotion. It returned 13 findings, all low severity: zero critical, zero high, zero medium. For a repository of this size that is a clean result.
Only two are direct dependencies of a published package, and they are the same library. packages/lambda-php/composer.json declares aws/aws-sdk-php at ^3.269 — a range that would resolve to current — but composer.lock pins 3.322.5, roughly forty-nine minor versions behind, carrying a CloudFront policy-document injection advisory fixed in 3.371.4 and an S3 encryption-client key-commitment issue (CVE-2025-14761) fixed in 3.368.0. A composer update closes both. The remaining findings sit in example packages: an old aws-sdk-go in the Go example, golang.org/x/net and x/text below their fixed versions, Ruby's json gem at 2.16.0, and two advisories against Rust crates in the compositor's lockfile, one of which is merely an unmaintained-crate notice.
The more interesting result is what the scan could not see. It parsed eight lockfiles — one Cargo.lock, two composer.lock, two go.sum, two Gemfile.lock and one requirements.txt — and not one of them was JavaScript. For a monorepo that is 64% TypeScript and 27% TSX, the dependency scan covered the Rust compositor and the PHP, Go, Ruby and Python Lambda clients, and none of the actual product.
The cause is that Remotion locks with Bun. Its only JavaScript lockfile is a 3.9 MB bun.lock at the repository root, and bun.lock is not in the scanner's supported set, which covers package-lock.json, npm-shrinkwrap.json, yarn.lock in both classic and berry formats, and pnpm-lock.yaml. No other JavaScript lockfile exists anywhere in the tree, so there was nothing to fall back to. This is not specific to Remotion — HyperFrames locks with Bun as well. The JavaScript dependency tree of both projects in this series is invisible to that scanner, and neither audit should be read as covering it. Stated plainly: we make no claim about CVEs in Remotion's npm dependencies, because none were examined.
The generalisable lesson is a tooling one, and it applies well beyond either project. A dependency scanner that silently succeeds on the eight lockfiles it recognises, while ignoring the one lockfile holding the project's primary ecosystem, produces a result that reads as coverage and is not. Two fixes follow. Parse bun.lock — it is a documented text format with resolved versions and integrity hashes, so this is a parser rather than a research project, and Bun's share of new JavaScript repositories makes it the highest-value ecosystem gap. And report coverage alongside findings: when a project's dominant language has no parsed lockfile, that belongs in the output as a warning, not as silence. A zero-finding result should always carry the denominator it was computed over.
Correction, 1 August 2026. An earlier version of this article stated that Remotion's usage metering is key-gated everywhere, summarising it as 'no key, no call — licence metering, not surveillance'. That was derived from the serverless handler alone and is wrong for @remotion/web-renderer, which reports unconditionally in a browser and includes the page origin. The passage above has been corrected. The error was ours: the browser-path file appeared in our own search results and we did not open it. Thanks to the independent audit that caught it.
📚 Sources & References
| # | Source | Link |
|---|---|---|
| [1] | Remotion — make videos programmatically with React |
|
| [2] | Code Indexer — semantic code search and analysis engine |
|