Still, you allow random people to open MRs.

On Tue, 2 Jun 2026, at 05:07, Romain Beauxis via ffmpeg-devel wrote:
> That being said, the workflows don't run by default.
>
> Adding Timo to get some context. Timo, who are the people who have rights
> to run CI workflows by default?
>
> Thanks,
> -- Romain
>
> Le lun. 1 juin 2026 à 22:05, Romain Beauxis <[email protected]> a
> écrit :
>
>> 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]

-- 
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