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]

Reply via email to