Skip to content

Commit c5e7901

Browse files
lucasgelfondclaude
andcommitted
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. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 0965a7d commit c5e7901

8 files changed

Lines changed: 93 additions & 48 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: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,27 @@ 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
19-
- name: Update pkg-config database
20-
run: sudo ldconfig
25+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26+
with:
27+
persist-credentials: false
2128
- name: Setup Docker Buildx
2229
id: buildx
23-
uses: docker/setup-buildx-action@v2
30+
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
2431
- name: Cache build
2532
id: cache-build
26-
uses: actions/cache@v4
33+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
2734
with:
2835
path: build-cache
2936
key: build-cache-v2-${{ hashFiles('Dockerfile', 'Makefile', 'build/*') }}
@@ -32,7 +39,7 @@ jobs:
3239
- name: Build ffmpeg-core
3340
run: make prd EXTRA_ARGS="--cache-from=type=local,src=build-cache --cache-to=type=local,dest=build-cache,mode=max"
3441
- name: Upload core
35-
uses: actions/upload-artifact@v4
42+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
3643
with:
3744
name: ffmpeg-core
3845
path: packages/core/dist/*
@@ -42,21 +49,22 @@ jobs:
4249
- build-core
4350
steps:
4451
- name: Checkout Source Code
45-
uses: actions/checkout@v4
52+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
4653
with:
54+
persist-credentials: false
4755
submodules: true
4856
- name: Download ffmpeg-core
49-
uses: actions/download-artifact@v4
57+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
5058
with:
5159
name: ffmpeg-core
5260
path: packages/core/dist
5361
- name: Use Node.js 20
54-
uses: actions/setup-node@v4
62+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
5563
with:
5664
node-version: 20.x
5765
- name: Cache dependencies
5866
id: cache-dependencies
59-
uses: actions/cache@v4
67+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
6068
with:
6169
path: node_modules
6270
key: node-modules-${{ hashFiles('package-lock.json') }}
@@ -76,18 +84,20 @@ jobs:
7684
- build-core
7785
steps:
7886
- name: Checkout Source Code
79-
uses: actions/checkout@v4
87+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
88+
with:
89+
persist-credentials: false
8090
- name: Download ffmpeg-core
81-
uses: actions/download-artifact@v4
91+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
8292
with:
8393
name: ffmpeg-core
8494
path: packages/core/dist
8595
- name: Use Node.js 20
86-
uses: actions/setup-node@v4
96+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
8797
with:
8898
node-version: 20.x
8999
- name: Cache FATE suite
90-
uses: actions/cache@v4
100+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
91101
with:
92102
path: .cache/fate-suite
93103
key: fate-suite-v1
@@ -100,7 +110,7 @@ jobs:
100110
run: npm run bench
101111
- name: Upload results
102112
if: always()
103-
uses: actions/upload-artifact@v4
113+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
104114
with:
105115
name: heavy-results
106116
path: tests/heavy/results

‎Dockerfile‎

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
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
# Parallel jobs for each `make` call, defaults to $(nproc) when empty.
811
ARG MAKE_JOBS
912
ENV INSTALL_DIR=/opt
10-
ENV FFMPEG_VERSION=n9.0.2
1113
# FFmpeg's command line tools need threads since 6.1, so everything is built with pthreads.
1214
ENV CFLAGS="-I$INSTALL_DIR/include -pthread $EXTRA_CFLAGS"
1315
ENV CXXFLAGS="$CFLAGS"
@@ -22,38 +24,44 @@ COPY build/meson-cross.ini /meson-cross.ini
2224

2325
# Build x264
2426
FROM emsdk-base AS x264-builder
27+
# stable branch
2528
ADD https://code.videolan.org/videolan/x264.git#b35605ace3ddf7c1a5d67a2eb553f034aef41d55 /src
2629
COPY build/x264.sh /src/build.sh
2730
RUN bash -x /src/build.sh
2831

2932
# Build x265
3033
FROM emsdk-base AS x265-builder
31-
ADD https://bitbucket.org/multicoreware/x265_git.git#4.2 /src
34+
# 4.2
35+
ADD https://bitbucket.org/multicoreware/x265_git.git#e444744c03978c1fb4e037168967020cf2648427 /src
3236
COPY build/x265.sh /src/build.sh
3337
RUN bash -x /src/build.sh
3438

3539
# Build libvpx
3640
FROM emsdk-base AS libvpx-builder
37-
ADD https://github.com/webmproject/libvpx.git#v1.17.0 /src
41+
# v1.17.0
42+
ADD https://github.com/webmproject/libvpx.git#6df3ec34557879fff673706f4a1d9fbd0f3a6f0e /src
3843
COPY build/libvpx.sh /src/build.sh
3944
RUN bash -x /src/build.sh
4045

4146
# Build lame (3.100, the latest release; there is no upstream git repository)
4247
FROM emsdk-base AS lame-builder
43-
ADD https://github.com/ffmpegwasm/lame.git#master /src
48+
# 3.100
49+
ADD https://github.com/ffmpegwasm/lame.git#2badea1974ae36cb8312afe99cff1e6b3b5decee /src
4450
COPY build/lame.sh /src/build.sh
4551
RUN bash -x /src/build.sh
4652

4753
# Build ogg
4854
FROM emsdk-base AS ogg-builder
49-
ADD https://github.com/xiph/ogg.git#v1.3.6 /src
55+
# v1.3.6
56+
ADD https://github.com/xiph/ogg.git#be05b13e98b048f0b5a0f5fa8ce514d56db5f822 /src
5057
COPY build/ogg.sh /src/build.sh
5158
RUN bash -x /src/build.sh
5259

5360
# Build theora
5461
FROM emsdk-base AS theora-builder
5562
COPY --from=ogg-builder $INSTALL_DIR $INSTALL_DIR
56-
ADD https://github.com/xiph/theora.git#v1.2.0 /src
63+
# v1.2.0
64+
ADD https://github.com/xiph/theora.git#8e4808736e9c181b971306cc3f05df9e61354004 /src
5765
COPY build/theora.sh /src/build.sh
5866
RUN bash -x /src/build.sh
5967

@@ -69,39 +77,45 @@ RUN bash -x /src/build.sh
6977
# Build vorbis
7078
FROM emsdk-base AS vorbis-builder
7179
COPY --from=ogg-builder $INSTALL_DIR $INSTALL_DIR
72-
ADD https://github.com/xiph/vorbis.git#v1.3.7 /src
80+
# v1.3.7
81+
ADD https://github.com/xiph/vorbis.git#0657aee69dec8508a0011f47f3b69d7538e9d262 /src
7382
COPY build/vorbis.sh /src/build.sh
7483
RUN bash -x /src/build.sh
7584

7685
# Build zlib
7786
FROM emsdk-base AS zlib-builder
78-
ADD https://github.com/madler/zlib.git#v1.3.2 /src
87+
# v1.3.2
88+
ADD https://github.com/madler/zlib.git#da607da739fa6047df13e66a2af6b8bec7c2a498 /src
7989
COPY build/zlib.sh /src/build.sh
8090
RUN bash -x /src/build.sh
8191

8292
# Build libwebp
8393
FROM emsdk-base AS libwebp-builder
8494
COPY --from=zlib-builder $INSTALL_DIR $INSTALL_DIR
85-
ADD https://github.com/webmproject/libwebp.git#v1.6.0 /src
95+
# v1.6.0
96+
ADD https://github.com/webmproject/libwebp.git#4fa21912338357f89e4fd51cf2368325b59e9bd9 /src
8697
COPY build/libwebp.sh /src/build.sh
8798
RUN bash -x /src/build.sh
8899

89100
# Build freetype2
90101
FROM emsdk-base AS freetype2-builder
91-
ADD https://github.com/freetype/freetype.git#VER-2-14-3 /src
102+
# VER-2-14-3
103+
ADD https://github.com/freetype/freetype.git#0a0221a1347e2f1e07c395263540026e9a0aa7c7 /src
92104
COPY build/freetype2.sh /src/build.sh
93105
RUN bash -x /src/build.sh
94106

95107
# Build fribidi
96108
FROM emsdk-base AS fribidi-builder
97-
ADD https://github.com/fribidi/fribidi.git#v1.0.17 /src
109+
# v1.0.17
110+
ADD https://github.com/fribidi/fribidi.git#b93119f5fdc7ea47672cc304c1455ffa6dfe7536 /src
98111
COPY build/fribidi.sh /src/build.sh
99112
RUN bash -x /src/build.sh
100113

101114
# Build harfbuzz (with FreeType, for FFmpeg's drawtext)
102115
FROM emsdk-base AS harfbuzz-builder
103116
COPY --from=freetype2-builder $INSTALL_DIR $INSTALL_DIR
104-
ADD https://github.com/harfbuzz/harfbuzz.git#14.5.0 /src
117+
# 14.5.0
118+
ADD https://github.com/harfbuzz/harfbuzz.git#863d3f7787c6df18d20e4535c5906bf3eb803bd5 /src
105119
COPY build/harfbuzz.sh /src/build.sh
106120
RUN bash -x /src/build.sh
107121

@@ -110,19 +124,22 @@ FROM emsdk-base AS libass-builder
110124
COPY --from=freetype2-builder $INSTALL_DIR $INSTALL_DIR
111125
COPY --from=fribidi-builder $INSTALL_DIR $INSTALL_DIR
112126
COPY --from=harfbuzz-builder $INSTALL_DIR $INSTALL_DIR
113-
ADD https://github.com/libass/libass.git#0.17.5 /src
127+
# 0.17.5
128+
ADD https://github.com/libass/libass.git#4a05d8127f525943ebf45fdc6497c9e665947f0d /src
114129
COPY build/libass.sh /src/build.sh
115130
RUN bash -x /src/build.sh
116131

117132
# Build zimg
118133
FROM emsdk-base AS zimg-builder
119-
ADD https://github.com/sekrit-twc/zimg.git#release-3.0.6 /src
134+
# release-3.0.6
135+
ADD https://github.com/sekrit-twc/zimg.git#f819b14e8f39d1282400b0d9543e8ef73c1b2bbd /src
120136
COPY build/zimg.sh /src/build.sh
121137
RUN bash -x /src/build.sh
122138

123139
# Base ffmpeg image with dependencies and source code populated.
124140
FROM emsdk-base AS ffmpeg-base
125-
ADD https://github.com/FFmpeg/FFmpeg.git#$FFMPEG_VERSION /src
141+
# n9.0.2
142+
ADD https://github.com/FFmpeg/FFmpeg.git#946fcce07b6dcd0331c8cc609192aeff5e1924f8 /src
126143
COPY --from=x264-builder $INSTALL_DIR $INSTALL_DIR
127144
COPY --from=x265-builder $INSTALL_DIR $INSTALL_DIR
128145
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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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 { useCore, execLogged } from "../../helpers.js";
4+
5+
export default function () {
6+
const ctx = useCore();
7+
8+
for (const url of ["http://127.0.0.1:9/x.mp4", "tcp://127.0.0.1:9", "rtmp://127.0.0.1/x"]) {
9+
it(`refuses ${url.split(":")[0]} inputs`, () => {
10+
const { ret, text } = execLogged(ctx.core, "-i", url, "-f", "null", "-");
11+
expect(ret).to.not.equal(0);
12+
expect(text).to.match(/Protocol not found|No such file|Invalid argument/);
13+
});
14+
}
15+
}

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ export default function () {
1919
expect((await ctx.ffmpeg.listDir(mnt)).map((n) => n.name)).to.deep.equal([".", ".."]);
2020
});
2121

22-
it("returns false for an unknown file system", async () => {
23-
await ctx.ffmpeg.createDir(`${ctx.dir}/m2`);
24-
expect(await ctx.ffmpeg.mount("NOPEFS", {}, `${ctx.dir}/m2`)).to.equal(false);
25-
});
22+
for (const fsType of ["NOPEFS", "__proto__", "constructor"]) {
23+
it(`returns false for the file system "${fsType}"`, async () => {
24+
await ctx.ffmpeg.createDir(`${ctx.dir}/m-${fsType}`);
25+
expect(await ctx.ffmpeg.mount(fsType, {}, `${ctx.dir}/m-${fsType}`)).to.equal(false);
26+
});
27+
}
2628
}

0 commit comments

Comments
 (0)