It's true. Do you think I should revert? I sanitize the type of file that we get in?
Le lun. 1 juin 2026 à 12:25, Jean-Baptiste Kempf via ffmpeg-devel < [email protected]> a écrit : > This is particularly dangerous. > This makes the CI prone to injection to files from random people. > > On Mon, 1 Jun 2026, at 17:41, Romain Beauxis via ffmpeg-cvslog wrote: > > This is an automated email from the git hooks/post-receive script. > > > > Git pushed a commit to branch master > > in repository ffmpeg. > > > > commit 78fff004f021fc9b5a3467317eaab7deb446c955 > > Author: Romain Beauxis <[email protected]> > > AuthorDate: Wed May 27 08:09:12 2026 -0500 > > Commit: Romain Beauxis <[email protected]> > > CommitDate: Mon Jun 1 10:40:57 2026 -0500 > > > > .forgejo: add support for ephemeral FATE samples via PR attachments > > > > Developers can attach sample files to a PR and list their target > paths > > within the fate-suite in a fate-samples block in the PR description: > > > > ```fate-samples > > vorbis/tos.ogg > > mov/some-new-sample.mov > > ``` > > > > A new inject-pr-samples.py script fetches the PR metadata from the > > Forgejo API, resolves each listed path to its matching attachment by > > filename, and downloads the files into the fate-suite directory > before > > FATE runs. > > > > The script validates that pr-number is an integer, that paths are > > relative, contain no '..', and are at most 3 components deep > (matching > > the deepest paths in the existing fate-suite). Attachment URLs are > > restricted to the code.ffmpeg.org domain. > > > > The script exports a new_samples=true/false output via > $FORGEJO_OUTPUT. > > After FATE completes, a final workflow step fails the run if any new > > sample was injected, reminding contributors to add their samples to > the > > official fate-suite before the PR can be merged. > > > > The script can also be used locally: > > SAMPLES=/path/to/fate-suite .forgejo/inject-pr-samples.py > <pr-number> > > --- > > .forgejo/inject-pr-samples.py | 174 > ++++++++++++++++++++++++++++++++++++++++++ > > .forgejo/workflows/test.yml | 18 +++++ > > 2 files changed, 192 insertions(+) > > > > diff --git a/.forgejo/inject-pr-samples.py > > b/.forgejo/inject-pr-samples.py > > new file mode 100755 > > index 0000000000..3f50067751 > > --- /dev/null > > +++ b/.forgejo/inject-pr-samples.py > > @@ -0,0 +1,174 @@ > > +#!/usr/bin/env python3 > > +# Copyright (c) 2026 Romain Beauxis <[email protected]> > > +# > > +# Redistribution and use in source and binary forms, with or without > > +# modification, are permitted provided that the following conditions > > are met: > > +# > > +# 1. Redistributions of source code must retain the above copyright > > notice, > > +# this list of conditions and the following disclaimer. > > +# 2. Redistributions in binary form must reproduce the above copyright > > notice, > > +# this list of conditions and the following disclaimer in the > > documentation > > +# and/or other materials provided with the distribution. > > +# > > +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS > > "AS IS" > > +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED > > TO, THE > > +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR > > PURPOSE > > +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR > > CONTRIBUTORS BE > > +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR > > +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF > > +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR > > BUSINESS > > +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER > > IN > > +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR > > OTHERWISE) > > +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED > > OF THE > > +# POSSIBILITY OF SUCH DAMAGE. > > + > > +"""Inject PR attachment samples into the fate-suite directory. > > + > > +Usage: inject-pr-samples.py <pr-number> > > + > > +Reads SAMPLES from the environment (defaults to fate-suite). For each > > path > > +listed in a ```fate-samples``` block in the PR description, downloads > > the > > +matching PR attachment into $SAMPLES/<path>. > > + > > +The PR description should contain a block like: > > + > > + ```fate-samples > > + vorbis/tos.ogg > > + mov/some-new-sample.mov > > + ``` > > + > > +Each filename must match a file attached to the PR. > > +""" > > + > > +import hashlib > > +import json > > +import os > > +import re > > +import sys > > +import tempfile > > +import urllib.request > > +from pathlib import Path, PurePosixPath > > + > > +FORGEJO_API = > > "https://code.ffmpeg.org/api/v1/repos/ffmpeg/ffmpeg/issues" > > +ATTACHMENT_BASE = "https://code.ffmpeg.org/attachments/" > > + > > + > > +def fetch_json(url): > > + with urllib.request.urlopen(url) as r: > > + return json.load(r) > > + > > + > > +def parse_fate_samples(body): > > + paths = [] > > + in_block = False > > + for line in body.splitlines(): > > + if line == "```fate-samples": > > + in_block = True > > + elif line == "```" and in_block: > > + break > > + elif in_block: > > + parts = line.split() > > + if len(parts) == 1: > > + paths.append(parts[0]) > > + return paths > > + > > + > > +MAX_PATH_DEPTH = 3 > > + > > + > > +def validate_path(path): > > + p = PurePosixPath(path) > > + if p.is_absolute(): > > + raise ValueError(f"path must be relative: {path!r}") > > + if ".." in p.parts: > > + raise ValueError(f"path must not contain '..': {path!r}") > > + if not p.parts: > > + raise ValueError(f"empty path") > > + if len(p.parts) > MAX_PATH_DEPTH: > > + raise ValueError(f"path too deep (max {MAX_PATH_DEPTH} > > components): {path!r}") > > + > > + > > +def validate_url(url): > > + if not url.startswith(ATTACHMENT_BASE): > > + raise ValueError(f"unexpected attachment URL: {url!r}") > > + > > + > > +def digest(path): > > + h = hashlib.sha256() > > + with open(path, "rb") as f: > > + while chunk := f.read(1 << 16): > > + h.update(chunk) > > + return h.digest() > > + > > + > > +def download(url, dst): > > + dst.parent.mkdir(parents=True, exist_ok=True) > > + with tempfile.NamedTemporaryFile(dir=dst.parent, delete=False) as > > tmp: > > + tmp_path = Path(tmp.name) > > + try: > > + with urllib.request.urlopen(url) as r: > > + while chunk := r.read(1 << 16): > > + tmp.write(chunk) > > + if dst.exists() and digest(dst) != digest(tmp_path): > > + raise ValueError(f"already exists with different > > content: {dst}") > > + tmp_path.rename(dst) > > + except: > > + tmp_path.unlink(missing_ok=True) > > + raise > > + > > + > > +def main(): > > + if len(sys.argv) != 2 or not re.fullmatch(r"[0-9]+", sys.argv[1]): > > + print(f"Usage: {sys.argv[0]} <pr-number>", file=sys.stderr) > > + sys.exit(1) > > + > > + pr_number = sys.argv[1] > > + samples_dir = Path(os.environ.get("SAMPLES", "fate-suite")) > > + > > + pr = fetch_json(f"{FORGEJO_API}/{pr_number}") > > + assets = {a["name"]: a["browser_download_url"] for a in > > pr.get("assets", [])} > > + paths = parse_fate_samples(pr.get("body", "")) > > + > > + if not paths: > > + sys.exit(0) > > + > > + new_samples = False > > + > > + for path in paths: > > + try: > > + validate_path(path) > > + except ValueError as e: > > + print(f"fate-samples: {e}", file=sys.stderr) > > + sys.exit(1) > > + > > + name = PurePosixPath(path).name > > + url = assets.get(name) > > + if url is None: > > + print(f"fate-samples: no attachment named {name!r}", > > file=sys.stderr) > > + sys.exit(1) > > + > > + try: > > + validate_url(url) > > + except ValueError as e: > > + print(f"fate-samples: {e}", file=sys.stderr) > > + sys.exit(1) > > + > > + dst = samples_dir / path > > + is_new = not dst.exists() > > + try: > > + download(url, dst) > > + except ValueError as e: > > + print(f"fate-samples: {e}", file=sys.stderr) > > + sys.exit(1) > > + if is_new: > > + new_samples = True > > + print(f"Injected: {path}") > > + > > + output_file = os.environ.get("FORGEJO_OUTPUT") > > + if output_file: > > + with open(output_file, "a") as f: > > + print(f"new_samples={'true' if new_samples else 'false'}", > > file=f) > > + > > + > > +if __name__ == "__main__": > > + main() > > diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml > > index 342120188e..3af1522b88 100644 > > --- a/.forgejo/workflows/test.yml > > +++ b/.forgejo/workflows/test.yml > > @@ -58,11 +58,20 @@ jobs: > > with: > > path: fate-suite > > key: fate-suite-${{ steps.fate.outputs.hash }} > > + - name: Inject PR Samples > > + id: inject > > + if: ${{ forge.event_name == 'pull_request' }} > > + run: SAMPLES=$PWD/fate-suite .forgejo/inject-pr-samples.py ${{ > > forge.event.pull_request.number }} > > - name: Run Fate > > run: | > > LD_LIBRARY_PATH="$(printf "%s:" "$PWD"/lib*)$PWD" make fate > > fate-build SAMPLES="$PWD/fate-suite" -j$(nproc) || FATERES=$? > > find . -name "*.err" -exec printf '::group::%s\n' {} \; > > -exec cat {} \; -exec printf '::endgroup::\n' \; > > exit ${FATERES:-0} > > + - name: Fail if new samples were injected > > + if: ${{ steps.inject.outputs.new_samples == 'true' }} > > + run: | > > + echo "New FATE samples were injected from PR attachments. > > Please add them to the official fate-suite before merging." > > + exit 1 > > run_fate_full: > > name: Fate (Full, ${{ matrix.target_exec }}) > > strategy: > > @@ -110,6 +119,10 @@ jobs: > > with: > > path: fate-suite > > key: fate-suite-${{ steps.fate.outputs.hash }} > > + - name: Inject PR Samples > > + id: inject > > + if: ${{ forge.event_name == 'pull_request' }} > > + run: SAMPLES=$PWD/fate-suite > > ffmpeg/.forgejo/inject-pr-samples.py ${{ > > forge.event.pull_request.number }} > > - name: Run Fate > > run: | > > if [[ "${{ matrix.target_exec }}" == "wine" ]]; then > > @@ -119,3 +132,8 @@ jobs: > > LD_LIBRARY_PATH="$(printf "%s:" "$PWD"/lib*)$PWD" make -C > > build fate fate-build SAMPLES="$PWD/fate-suite" -j$(nproc) || FATERES=$? > > find . -name "*.err" -exec printf '::group::%s\n' {} \; > > -exec cat {} \; -exec printf '::endgroup::\n' \; > > exit ${FATERES:-0} > > + - name: Fail if new samples were injected > > + if: ${{ steps.inject.outputs.new_samples == 'true' }} > > + run: | > > + echo "New FATE samples were injected from PR attachments. > > Please add them to the official fate-suite before merging." > > + exit 1 > > > > _______________________________________________ > > ffmpeg-cvslog mailing list -- [email protected] > > To unsubscribe send an email to [email protected] > > -- > Jean-Baptiste Kempf - President > +33 672 704 734 > https://jbkempf.com/ > _______________________________________________ > ffmpeg-devel mailing list -- [email protected] > To unsubscribe send an email to [email protected] > _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
