diff --git a/.github/workflows/image-security.yml b/.github/workflows/image-security.yml index 5395800..a9a3062 100644 --- a/.github/workflows/image-security.yml +++ b/.github/workflows/image-security.yml @@ -2,6 +2,12 @@ name: Image security on: workflow_dispatch: + workflow_call: + pull_request: + paths: + - Dockerfile + - deploy/encoding-booster/encoder/** + - .github/workflows/image-security.yml schedule: - cron: "41 6 * * 1" @@ -35,8 +41,9 @@ jobs: DOCKERFILE: ${{ matrix.dockerfile }} BUILD_CONTEXT: ${{ matrix.context }} run: docker build --pull --file "$DOCKERFILE" --tag "mave-scan:$IMAGE_NAME" "$BUILD_CONTEXT" - - uses: aquasecurity/trivy-action@d2a0b60797ff03db6132bd4e2b293f9b37081297 + - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: + version: v0.75.0 image-ref: mave-scan:${{ matrix.name }} scanners: vuln severity: HIGH,CRITICAL diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1e177e2..57bfbbf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,6 +34,9 @@ jobs: uses: ./.github/workflows/security.yml with: release_version: ${{ needs.version.outputs.version }} + image-security: + needs: version + uses: ./.github/workflows/image-security.yml self-hosted: needs: version runs-on: ubuntu-24.04 @@ -72,7 +75,7 @@ jobs: print(f'::group::{name}\n{text}\n::endgroup::') PY release: - needs: [version, checks, self-hosted] + needs: [version, checks, image-security, self-hosted] runs-on: ubuntu-24.04 permissions: contents: write diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index f79f50f..7a4abd5 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -37,7 +37,7 @@ jobs: trap 'sudo rm -rf "$artifacts"' EXIT docker run --rm \ -v "$PWD/deploy/encoding-booster/encoder/sandbox/broker:/src:ro" \ - -v "$artifacts:/out" -w /src golang:1.25.13-alpine3.23 \ + -v "$artifacts:/out" -w /src golang:1.27.2-alpine3.23 \ sh -c 'go test ./... && go vet ./... && CGO_ENABLED=0 go build -trimpath -o /out/mave-media-broker .' cc -O2 -Wall -Wextra -Werror deploy/encoding-booster/encoder/sandbox/media_sandbox.c \ -lseccomp -o "$artifacts/mave-media-sandbox" @@ -105,8 +105,8 @@ jobs: run: python3 .github/scripts/release.py apply - uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1 with: - otp-version: "28.2" - elixir-version: "1.19.4" + otp-version: "29.1.1" + elixir-version: "1.20.4" - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: "24.21.0" @@ -117,7 +117,7 @@ jobs: deps _build priv/ua_inspector - key: test-${{ runner.os }}-otp28.2-elixir1.19.4-${{ hashFiles('mix.lock') }} + key: test-${{ runner.os }}-otp29.1.1-elixir1.20.4-${{ hashFiles('mix.lock') }} - name: Prepare disposable media dependencies run: | sudo apt-get update @@ -161,8 +161,8 @@ jobs: run: python3 .github/scripts/release.py apply - uses: erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124 # v1 with: - otp-version: "28.2" - elixir-version: "1.19.4" + otp-version: "29.1.1" + elixir-version: "1.20.4" - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: "24.21.0" diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..cea54f2 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,3 @@ +erlang 29.1.1 +elixir 1.20.4-otp-29 +nodejs 24.21.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f03db91..ba56338 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,7 +46,7 @@ development services publicly. Requirements: -- Docker with Compose v2; Elixir 1.19.4 and Erlang/OTP 28.2, as pinned in the +- Docker with Compose v2; Elixir 1.20.4 and Erlang/OTP 29.1.1, as pinned in the [Dockerfile](Dockerfile). - Node.js/npm, a C compiler, and Rust/Cargo. - FFmpeg and FFprobe on `PATH`, with H.264 encoding (`libx264`). diff --git a/Dockerfile b/Dockerfile index c21e2d4..c606c97 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,11 +9,11 @@ # - https://hub.docker.com/r/hexpm/elixir/tags - for the build image # - https://hub.docker.com/_/ubuntu/tags?name=26.04 - for the release image # - https://pkgs.org/ - resource for finding needed packages -# - Ex: docker.io/hexpm/elixir:1.19.4-erlang-28.2-debian-trixie-20251208-slim +# - Ex: docker.io/hexpm/elixir:1.20.4-erlang-29.1.1-debian-trixie-20261005-slim # -ARG ELIXIR_VERSION=1.19.4 -ARG OTP_VERSION=28.2 -ARG DEBIAN_VERSION=trixie-20251208-slim +ARG ELIXIR_VERSION=1.20.4 +ARG OTP_VERSION=29.1.1 +ARG DEBIAN_VERSION=trixie-20261005-slim ARG FFMPEG_VERSION=9.0.2 ARG VERSION=dev ARG VCS_REF=unknown @@ -26,7 +26,7 @@ ARG RUNNER_IMAGE="docker.io/ubuntu:26.04@sha256:da6fc2be547864451aa253836dd926da FROM node:24.21.0-bookworm-slim@sha256:0e0ff40c39bc087845bfb27465a0df4ea419520094bc35842ff83dd8cbe6f9b6 AS node_build -FROM golang:1.25.13-alpine3.23 AS media_broker_build +FROM golang:1.27.2-alpine3.23 AS media_broker_build WORKDIR /src COPY deploy/encoding-booster/encoder/sandbox/broker/ ./ RUN go test ./... && CGO_ENABLED=0 go build -trimpath -o /mave-media-broker . @@ -194,6 +194,10 @@ RUN apt-get update \ # the compiled release and other runtime necessities FROM ${RUNNER_IMAGE} AS runtime_base +# The base image includes Pebble, but this release runs under tini instead. +# Remove the unused Go binary so it cannot retain an unpatched Go runtime. +RUN rm -f /usr/bin/pebble + ARG FFMPEG_VERSION RUN apt-get update \ && apt-get upgrade -y --no-install-recommends \ @@ -204,6 +208,7 @@ RUN apt-get update \ libmp3lame0 \ libncurses6 \ libseccomp2 \ + libsctp1 \ libnuma1 \ libopus0 \ libsharpyuv0 \ diff --git a/deploy/encoding-booster/encoder/Dockerfile b/deploy/encoding-booster/encoder/Dockerfile index f750750..90ca25f 100644 --- a/deploy/encoding-booster/encoder/Dockerfile +++ b/deploy/encoding-booster/encoder/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25.13-alpine3.23 AS app-build +FROM golang:1.27.2-alpine3.23 AS app-build WORKDIR /src COPY go.mod *.go ./ COPY assets/play.png ./assets/play.png diff --git a/deploy/encoding-booster/encoder/Dockerfile.gpu b/deploy/encoding-booster/encoder/Dockerfile.gpu index f7cddbe..5339bf0 100644 --- a/deploy/encoding-booster/encoder/Dockerfile.gpu +++ b/deploy/encoding-booster/encoder/Dockerfile.gpu @@ -1,4 +1,4 @@ -FROM golang:1.25.13-alpine3.23 AS app-build +FROM golang:1.27.2-alpine3.23 AS app-build WORKDIR /src COPY go.mod *.go ./ diff --git a/deploy/minio/Dockerfile b/deploy/minio/Dockerfile index f69364e..337b970 100644 --- a/deploy/minio/Dockerfile +++ b/deploy/minio/Dockerfile @@ -1,10 +1,10 @@ # The community images are no longer publicly pullable. Build the same pinned # releases from upstream source; Go's checksum database verifies module contents. -FROM golang:1.25.13-alpine3.23 AS server-build +FROM golang:1.27.2-alpine3.23 AS server-build ENV CGO_ENABLED=0 GOTOOLCHAIN=local RUN go install -trimpath github.com/minio/minio@v0.0.0-20250907161309-07c3a429bfed -FROM golang:1.25.13-alpine3.23 AS client-build +FROM golang:1.27.2-alpine3.23 AS client-build ENV CGO_ENABLED=0 GOTOOLCHAIN=local RUN go install -trimpath github.com/minio/mc@v0.0.0-20250813083541-7394ce0dd2a8 diff --git a/docs/security_checks.md b/docs/security_checks.md index 902d5a0..23d0682 100644 --- a/docs/security_checks.md +++ b/docs/security_checks.md @@ -1,7 +1,7 @@ # Security checks Pull requests run the complete test suite against disposable PostgreSQL, -ClickHouse and MinIO services, with Elixir 1.19.4 / OTP 28.2 and FFmpeg. +ClickHouse and MinIO services, with Elixir 1.20.4 / OTP 29.1.1 and FFmpeg. CI and the shared Core/SaaS asset builder use Node 24.21.0 LTS; the official checkout/setup-node/cache Actions use Node 24 as well. Production Core, CPU and GPU images use FFmpeg 9.0.2. The GPU build retains Ubuntu 24.04's NVIDIA headers @@ -10,7 +10,9 @@ Compilation warnings, formatting, Credo, dependency advisories, high-severity npm advisories and newly committed secrets fail their respective jobs. Scanner output redacts secret values. Scheduled jobs scan the current tree and locally built Core/CPU/GPU images, including unfixed high/critical OS and native-library -advisories. Images are never published or deployed by these workflows. +advisories. Image scans also run for Dockerfile/encoder pull requests and gate +Core releases. Trivy is pinned to 0.75.0; Go binaries use Go 1.27.2. +Images are never published or deployed by these workflows. Actions are pinned to reviewed commit SHAs. PR jobs have read-only repository permissions, do not persist checkout credentials, and receive no production diff --git a/lib/mave_core/encoding_booster/bundle.ex b/lib/mave_core/encoding_booster/bundle.ex index c004b56..3d45753 100644 --- a/lib/mave_core/encoding_booster/bundle.ex +++ b/lib/mave_core/encoding_booster/bundle.ex @@ -155,15 +155,24 @@ defmodule MaveCore.EncodingBooster.Bundle do cwd: String.to_charlist(extract_root), file_list: file_list ) do - {:ok, _files} -> :ok - {:error, :badarg} -> extract_streamed_store_entries(bundle_path, extract_root, entries) - {:error, reason} -> {:error, {:invalid_encoding_booster_bundle, reason}} + {:ok, _files} -> + :ok + + {:error, :badarg} -> + extract_streamed_store_entries(bundle_path, extract_root, entries) + + {:error, {_path, {:file, :write_file_info, _args}, :badarg}} -> + extract_streamed_store_entries(bundle_path, extract_root, entries) + + {:error, reason} -> + {:error, {:invalid_encoding_booster_bundle, reason}} end end # Go's archive/zip writes data descriptors when entries are streamed to an # HTTP response. OTP can list those archives but :zip.extract/2 rejects them - # with :badarg, so copy the already-validated stored entries by their central + # with :badarg (including invalid zero-date metadata in newer OTP versions), + # so copy the already-validated stored entries by their central # directory offsets and verify each data descriptor while doing so. defp extract_streamed_store_entries(bundle_path, extract_root, entries) do with {:ok, safe_bundle_path} <- SafeFile.readable_path(bundle_path), diff --git a/lib/mave_core/safe_file.ex b/lib/mave_core/safe_file.ex index 75a79e9..2fdc7cb 100644 --- a/lib/mave_core/safe_file.ex +++ b/lib/mave_core/safe_file.ex @@ -39,7 +39,7 @@ defmodule MaveCore.SafeFile do # sobelow_skip ["Traversal.FileModule"] def stream_read!(path, modes \\ [], chunk_size \\ @default_stream_chunk) do {:ok, expanded} = readable_path(path) - File.stream!(expanded, modes, chunk_size) + File.stream!(expanded, chunk_size, modes) end # sobelow_skip ["Traversal.FileModule"] diff --git a/mix.exs b/mix.exs index 18cdc58..eb48a75 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule MaveCore.MixProject do [ app: :mave_core, version: "0.2.1", - elixir: "~> 1.15", + elixir: "~> 1.20", elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, aliases: aliases(), diff --git a/test/mave_core/encoding_booster_test.exs b/test/mave_core/encoding_booster_test.exs index e9597c0..3f8e049 100644 --- a/test/mave_core/encoding_booster_test.exs +++ b/test/mave_core/encoding_booster_test.exs @@ -685,6 +685,35 @@ defmodule MaveCore.EncodingBoosterTest do File.rm_rf!(output_path <> ".bundle") end + test "rejects corrupted streamed ZIP entries with zero-date metadata", %{ + output_path: output_path + } do + configure_booster() + Req.default_options(plug: {Req.Test, __MODULE__}) + + bundle = + :binary.replace( + go_streaming_encoding_bundle(), + <<"segment", 0x50, 0x4B, 0x07, 0x08>>, + <<"corrupt", 0x50, 0x4B, 0x07, 0x08>> + ) + + Req.Test.expect(__MODULE__, fn conn -> + conn + |> put_resp_header("content-type", "application/vnd.mave.encoding-bundle+zip") + |> send_resp(200, bundle) + end) + + assert {:error, :incomplete_encoding_booster_bundle} = + EncodingBooster.encode_to_file( + "https://source.example/video.mp4", + output_path, + package_hls: true + ) + + refute File.exists?(output_path) + end + test "packages an encoded rendition through the HLS-only booster endpoint", %{ output_path: output_path } do