Skip to content

Commit d910c46

Browse files
committed
Security: pin every build input, disable network protocols, harden CI
From a security review of the repository (findings and non-goals in the PR). - P0, supply chain: the Dockerfile frontend, the emsdk image (by digest), every git source (by commit) and the opus tarball (by checksum) are pinned, so a moved tag or branch can't change a release. - The core is built with --disable-network: untrusted arguments or playlists could make the worker open WebSockets to arbitrary hosts, and network protocols can't work in a browser anyway. - CI: least-privilege token (contents: read), checkout without persisted credentials, actions pinned by SHA at their latest releases (plus a dependabot config to keep them current), superseded PR runs cancelled. - mount() only accepts file systems the core actually has; names such as "__proto__" returned an Object's prototype and broke FS.mount. - fetchFile(): drop the base64 branch, whose regex never matched a real data URL; fetch() handles data: URLs.
1 parent 47ffc0f commit d910c46

8 files changed

Lines changed: 93 additions & 50 deletions

File tree

‎.github/dependabot.yml‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
version: 2
2+
updates:
3+
# Keeps the SHA-pinned actions in .github/workflows up to date.
4+
- package-ecosystem: github-actions
5+
directory: /
6+
schedule:
7+
interval: weekly

‎.github/workflows/CI.yml‎

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,29 @@ on:
1010
- cron: "0 6 * * *"
1111
workflow_dispatch:
1212

13+
permissions:
14+
contents: read
15+
16+
concurrency:
17+
group: ${{ github.workflow }}-${{ github.ref }}
18+
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
19+
1320
jobs:
1421
build-core:
1522
runs-on: ubuntu-latest
1623
steps:
1724
- name: Checkout Source Code
18-
uses: actions/checkout@v2
25+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26+
with:
27+
persist-credentials: false
1928
- name: Update pkg-config database
2029
run: sudo ldconfig
2130
- name: Setup Docker Buildx
2231
id: buildx
23-
uses: docker/setup-buildx-action@v2
32+
uses: docker/setup-buildx-action@f87e5991ea3c3d8be0e5e2e2d1b6c4ecb9ac1a5f # v4.4.1
2433
- name: Cache build
2534
id: cache-build
26-
uses: actions/cache@v4
35+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
2736
with:
2837
path: build-cache
2938
key: build-cache-v2-${{ hashFiles('Dockerfile', 'Makefile', 'build/*') }}
@@ -32,7 +41,7 @@ jobs:
3241
- name: Build ffmpeg-core
3342
run: make prd EXTRA_ARGS="--cache-from=type=local,src=build-cache --cache-to=type=local,dest=build-cache,mode=max"
3443
- name: Upload core
35-
uses: actions/upload-artifact@v4
44+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
3645
with:
3746
name: ffmpeg-core
3847
path: packages/core/dist/*
@@ -44,15 +53,16 @@ jobs:
4453
matrix:
4554
node: [22, 24, 26]
4655
steps:
47-
- uses: actions/checkout@v4
56+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
4857
with:
58+
persist-credentials: false
4959
submodules: true
50-
- uses: actions/download-artifact@v4
60+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
5161
with:
5262
name: ffmpeg-core
5363
path: packages/core/dist
54-
- uses: pnpm/action-setup@v6
55-
- uses: actions/setup-node@v4
64+
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
65+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
5666
with:
5767
node-version: ${{ matrix.node }}
5868
cache: pnpm
@@ -63,15 +73,16 @@ jobs:
6373
runs-on: ubuntu-latest
6474
needs: [build-core]
6575
steps:
66-
- uses: actions/checkout@v4
76+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
6777
with:
78+
persist-credentials: false
6879
submodules: true
69-
- uses: actions/download-artifact@v4
80+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
7081
with:
7182
name: ffmpeg-core
7283
path: packages/core/dist
73-
- uses: pnpm/action-setup@v6
74-
- uses: actions/setup-node@v4
84+
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
85+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
7586
with:
7687
node-version: 24
7788
cache: pnpm
@@ -85,17 +96,19 @@ jobs:
8596
runs-on: ubuntu-latest
8697
needs: [build-core]
8798
steps:
88-
- uses: actions/checkout@v4
89-
- uses: actions/download-artifact@v4
99+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
100+
with:
101+
persist-credentials: false
102+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
90103
with:
91104
name: ffmpeg-core
92105
path: packages/core/dist
93-
- uses: pnpm/action-setup@v6
94-
- uses: actions/setup-node@v4
106+
- uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0
107+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
95108
with:
96109
node-version: 24
97110
cache: pnpm
98-
- uses: actions/cache@v4
111+
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
99112
with:
100113
path: .cache/fate-suite
101114
key: fate-suite-v1
@@ -106,7 +119,7 @@ jobs:
106119
- name: Benchmarks
107120
if: always()
108121
run: pnpm bench
109-
- uses: actions/upload-artifact@v4
122+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
110123
if: always()
111124
with:
112125
name: heavy-results

‎Dockerfile‎

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
# syntax=docker/dockerfile-upstream:master-labs
1+
# syntax=docker/dockerfile:1.10@sha256:865e5dd094beca432e8c0a1d5e1c465db5f998dca4e439981029b3b81fb39ed5
2+
3+
# Every input is pinned (image digests, git commits, tarball checksums) so a
4+
# release can't change underneath us. The comments name the pinned versions.
25

36
# Base emsdk image with environment variables.
4-
FROM emscripten/emsdk:6.0.10 AS emsdk-base
7+
FROM emscripten/emsdk:6.0.10@sha256:e077d54e2b8970575ebc4f185ac1de0b95c05f2b266134d4ba27449af7aebf65 AS emsdk-base
58
ARG EXTRA_CFLAGS
69
ARG EXTRA_LDFLAGS
710
ENV INSTALL_DIR=/opt
8-
ENV FFMPEG_VERSION=n9.0.2
911
# FFmpeg's command line tools need threads since 6.1, so everything is built with pthreads.
1012
ENV CFLAGS="-I$INSTALL_DIR/include -pthread $EXTRA_CFLAGS"
1113
ENV CXXFLAGS="$CFLAGS"
@@ -19,38 +21,44 @@ COPY build/meson-cross.ini /meson-cross.ini
1921

2022
# Build x264
2123
FROM emsdk-base AS x264-builder
24+
# stable branch
2225
ADD https://code.videolan.org/videolan/x264.git#b35605ace3ddf7c1a5d67a2eb553f034aef41d55 /src
2326
COPY build/x264.sh /src/build.sh
2427
RUN bash -x /src/build.sh
2528

2629
# Build x265
2730
FROM emsdk-base AS x265-builder
28-
ADD https://bitbucket.org/multicoreware/x265_git.git#4.2 /src
31+
# 4.2
32+
ADD https://bitbucket.org/multicoreware/x265_git.git#e444744c03978c1fb4e037168967020cf2648427 /src
2933
COPY build/x265.sh /src/build.sh
3034
RUN bash -x /src/build.sh
3135

3236
# Build libvpx
3337
FROM emsdk-base AS libvpx-builder
34-
ADD https://github.com/webmproject/libvpx.git#v1.17.0 /src
38+
# v1.17.0
39+
ADD https://github.com/webmproject/libvpx.git#6df3ec34557879fff673706f4a1d9fbd0f3a6f0e /src
3540
COPY build/libvpx.sh /src/build.sh
3641
RUN bash -x /src/build.sh
3742

3843
# Build lame (3.100, the latest release; there is no upstream git repository)
3944
FROM emsdk-base AS lame-builder
40-
ADD https://github.com/ffmpegwasm/lame.git#master /src
45+
# 3.100
46+
ADD https://github.com/ffmpegwasm/lame.git#2badea1974ae36cb8312afe99cff1e6b3b5decee /src
4147
COPY build/lame.sh /src/build.sh
4248
RUN bash -x /src/build.sh
4349

4450
# Build ogg
4551
FROM emsdk-base AS ogg-builder
46-
ADD https://github.com/xiph/ogg.git#v1.3.6 /src
52+
# v1.3.6
53+
ADD https://github.com/xiph/ogg.git#be05b13e98b048f0b5a0f5fa8ce514d56db5f822 /src
4754
COPY build/ogg.sh /src/build.sh
4855
RUN bash -x /src/build.sh
4956

5057
# Build theora
5158
FROM emsdk-base AS theora-builder
5259
COPY --from=ogg-builder $INSTALL_DIR $INSTALL_DIR
53-
ADD https://github.com/xiph/theora.git#v1.2.0 /src
60+
# v1.2.0
61+
ADD https://github.com/xiph/theora.git#8e4808736e9c181b971306cc3f05df9e61354004 /src
5462
COPY build/theora.sh /src/build.sh
5563
RUN bash -x /src/build.sh
5664

@@ -66,39 +74,45 @@ RUN bash -x /src/build.sh
6674
# Build vorbis
6775
FROM emsdk-base AS vorbis-builder
6876
COPY --from=ogg-builder $INSTALL_DIR $INSTALL_DIR
69-
ADD https://github.com/xiph/vorbis.git#v1.3.7 /src
77+
# v1.3.7
78+
ADD https://github.com/xiph/vorbis.git#0657aee69dec8508a0011f47f3b69d7538e9d262 /src
7079
COPY build/vorbis.sh /src/build.sh
7180
RUN bash -x /src/build.sh
7281

7382
# Build zlib
7483
FROM emsdk-base AS zlib-builder
75-
ADD https://github.com/madler/zlib.git#v1.3.2 /src
84+
# v1.3.2
85+
ADD https://github.com/madler/zlib.git#da607da739fa6047df13e66a2af6b8bec7c2a498 /src
7686
COPY build/zlib.sh /src/build.sh
7787
RUN bash -x /src/build.sh
7888

7989
# Build libwebp
8090
FROM emsdk-base AS libwebp-builder
8191
COPY --from=zlib-builder $INSTALL_DIR $INSTALL_DIR
82-
ADD https://github.com/webmproject/libwebp.git#v1.6.0 /src
92+
# v1.6.0
93+
ADD https://github.com/webmproject/libwebp.git#4fa21912338357f89e4fd51cf2368325b59e9bd9 /src
8394
COPY build/libwebp.sh /src/build.sh
8495
RUN bash -x /src/build.sh
8596

8697
# Build freetype2
8798
FROM emsdk-base AS freetype2-builder
88-
ADD https://github.com/freetype/freetype.git#VER-2-14-3 /src
99+
# VER-2-14-3
100+
ADD https://github.com/freetype/freetype.git#0a0221a1347e2f1e07c395263540026e9a0aa7c7 /src
89101
COPY build/freetype2.sh /src/build.sh
90102
RUN bash -x /src/build.sh
91103

92104
# Build fribidi
93105
FROM emsdk-base AS fribidi-builder
94-
ADD https://github.com/fribidi/fribidi.git#v1.0.17 /src
106+
# v1.0.17
107+
ADD https://github.com/fribidi/fribidi.git#b93119f5fdc7ea47672cc304c1455ffa6dfe7536 /src
95108
COPY build/fribidi.sh /src/build.sh
96109
RUN bash -x /src/build.sh
97110

98111
# Build harfbuzz (with FreeType, for FFmpeg's drawtext)
99112
FROM emsdk-base AS harfbuzz-builder
100113
COPY --from=freetype2-builder $INSTALL_DIR $INSTALL_DIR
101-
ADD https://github.com/harfbuzz/harfbuzz.git#14.5.0 /src
114+
# 14.5.0
115+
ADD https://github.com/harfbuzz/harfbuzz.git#863d3f7787c6df18d20e4535c5906bf3eb803bd5 /src
102116
COPY build/harfbuzz.sh /src/build.sh
103117
RUN bash -x /src/build.sh
104118

@@ -107,19 +121,22 @@ FROM emsdk-base AS libass-builder
107121
COPY --from=freetype2-builder $INSTALL_DIR $INSTALL_DIR
108122
COPY --from=fribidi-builder $INSTALL_DIR $INSTALL_DIR
109123
COPY --from=harfbuzz-builder $INSTALL_DIR $INSTALL_DIR
110-
ADD https://github.com/libass/libass.git#0.17.5 /src
124+
# 0.17.5
125+
ADD https://github.com/libass/libass.git#4a05d8127f525943ebf45fdc6497c9e665947f0d /src
111126
COPY build/libass.sh /src/build.sh
112127
RUN bash -x /src/build.sh
113128

114129
# Build zimg
115130
FROM emsdk-base AS zimg-builder
116-
ADD https://github.com/sekrit-twc/zimg.git#release-3.0.6 /src
131+
# release-3.0.6
132+
ADD https://github.com/sekrit-twc/zimg.git#f819b14e8f39d1282400b0d9543e8ef73c1b2bbd /src
117133
COPY build/zimg.sh /src/build.sh
118134
RUN bash -x /src/build.sh
119135

120136
# Base ffmpeg image with dependencies and source code populated.
121137
FROM emsdk-base AS ffmpeg-base
122-
ADD https://github.com/FFmpeg/FFmpeg.git#$FFMPEG_VERSION /src
138+
# n9.0.2
139+
ADD https://github.com/FFmpeg/FFmpeg.git#946fcce07b6dcd0331c8cc609192aeff5e1924f8 /src
123140
COPY --from=x264-builder $INSTALL_DIR $INSTALL_DIR
124141
COPY --from=x265-builder $INSTALL_DIR $INSTALL_DIR
125142
COPY --from=libvpx-builder $INSTALL_DIR $INSTALL_DIR

‎build/ffmpeg.sh‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ CONF_FLAGS=(
1414
--disable-runtime-cpudetect # disable cpu detection
1515
--disable-autodetect # disable env auto detect
1616
--pkg-config-flags=--static # static libraries also need their private dependencies
17+
--disable-network # no sockets in a browser; untrusted args could otherwise open WebSockets
1718

1819
# assign toolchains and extra flags
1920
--nm=emnm

‎packages/ffmpeg/src/worker.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,9 @@ const deleteDir = ({ path }: FFMessageDeleteDirData): OK => {
151151
};
152152

153153
const mount = ({ fsType, options, mountPoint }: FFMessageMountData): OK => {
154-
const str = fsType as keyof typeof ffmpeg.FS.filesystems;
155-
const fs = ffmpeg.FS.filesystems[str];
156-
if (!fs) return false;
154+
// Only the file systems the core links; names like "__proto__" are not.
155+
if (!Object.hasOwn(ffmpeg.FS.filesystems, fsType)) return false;
156+
const fs = ffmpeg.FS.filesystems[fsType as keyof typeof ffmpeg.FS.filesystems];
157157
ffmpeg.FS.mount(fs, options, mountPoint);
158158
return true;
159159
};

‎packages/util/src/index.ts‎

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,8 @@ export const fetchFile = async (
5252
let data: ArrayBuffer | number[];
5353

5454
if (typeof file === "string") {
55-
/* From base64 format */
56-
if (/data:_data\/([a-zA-Z]*);base64,([^"]*)/.test(file)) {
57-
data = atob(file.split(",")[1])
58-
.split("")
59-
.map((c) => c.charCodeAt(0));
60-
/* From remote server/URL */
61-
} else {
62-
data = await (await fetch(file)).arrayBuffer();
63-
}
55+
/* From a URL, including data: URLs */
56+
data = await (await fetch(file)).arrayBuffer();
6457
} else if (file instanceof URL) {
6558
data = await (await fetch(file)).arrayBuffer();
6659
} else if (file instanceof File || file instanceof Blob) {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// The core is built with --disable-network: untrusted arguments or playlists
2+
// can't make it open connections (it used to emulate sockets with WebSockets).
3+
import { expect } from "vitest";
4+
import { test } from "../../helpers/core.js";
5+
import { exec } from "../../helpers/run.js";
6+
7+
test.for(["http://127.0.0.1:9/x.mp4", "tcp://127.0.0.1:9", "rtmp://127.0.0.1/x"])("refuses %s", (url, { core }) => {
8+
const { ret, log } = exec(core, "-i", url, "-f", "null", "-");
9+
expect(ret).to.not.equal(0);
10+
expect(log).to.match(/Protocol not found|No such file|Invalid argument/);
11+
});

‎tests/ffmpeg/mount/workerfs.test.js‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ test("mounts Files and Blobs with WORKERFS and ffmpeg reads them", async ({ ffmp
1717
expect((await ffmpeg.listDir(mnt)).map((n) => n.name)).to.deep.equal([".", ".."]);
1818
});
1919

20-
test("returns false for an unknown file system", async ({ ffmpeg, dir }) => {
21-
await ffmpeg.createDir(`${dir}/m2`);
22-
expect(await ffmpeg.mount("NOPEFS", {}, `${dir}/m2`)).to.equal(false);
20+
// Only the file systems the core links; not arbitrary FS properties.
21+
test.for(["NOPEFS", "__proto__", "constructor"])("returns false for the file system %s", async (fsType, { ffmpeg, dir }) => {
22+
await ffmpeg.createDir(`${dir}/m`);
23+
expect(await ffmpeg.mount(fsType, {}, `${dir}/m`)).to.equal(false);
2324
});

0 commit comments

Comments
 (0)