From 304f423146154bedaa59f9acefc9a993e7061a8f Mon Sep 17 00:00:00 2001 From: Paillat-dev Date: Mon, 11 May 2026 13:59:38 +0200 Subject: [PATCH 1/7] feat: Add sensible ffmpeg protocol whitelist and better docs --- discord/player.py | 103 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/discord/player.py b/discord/player.py index f80bba9c1f..9d89eb1d7a 100644 --- a/discord/player.py +++ b/discord/player.py @@ -38,7 +38,7 @@ import time import warnings from math import floor -from typing import IO, TYPE_CHECKING, Any, Callable, Generic, TypeVar +from typing import IO, TYPE_CHECKING, Any, Callable, Generic, Literal, TypeVar, overload from .enums import SpeakingState from .errors import ClientException @@ -309,6 +309,11 @@ def cleanup(self) -> None: self._process = self._stdout = self._stdin = self._stderr = MISSING +DEFAULT_PROTOCOL_WHITELIST: set[str] = frozenset( + {"file", "http", "https", "tcp", "tls", "crypto", "pipe", "fd", "cache"} +) + + class FFmpegPCMAudio(FFmpegAudio): """An audio source from FFmpeg (or AVConv). @@ -325,6 +330,16 @@ class FFmpegPCMAudio(FFmpegAudio): The input that ffmpeg will take and convert to PCM bytes. If ``pipe`` is ``True`` then this is a file-like object that is passed to the stdin of ffmpeg. + + .. warning:: + + The ``source`` parameter is passed directly to your executable's ``-i`` flag and + interpreted through its full protocol machinery. This means it can accept + ``file://`` paths (local file read), ``http(s)://`` to internal services + (SSRF), ``concat:`` (multi-file read), and other dangerous schemes. + You should never pass attacker-controlled values (e.g. raw user input from a chat + command) to ``source`` without validation. + executable: :class:`str` The executable name (and path) to use. Defaults to ``ffmpeg``. @@ -342,6 +357,12 @@ class FFmpegPCMAudio(FFmpegAudio): Extra command line arguments to pass to ffmpeg before the ``-i`` flag. options: Optional[:class:`str`] Extra command line arguments to pass to ffmpeg after the ``-i`` flag. + protocol_whitelist: Optional[:class:`str`] + A comma-separated list of protocols that ffmpeg is allowed to use. + Defaults to ``"file,http,https,tcp,tls,crypto,pipe,fd,cache"``, which + blocks dangerous schemes such as ``concat:``, ``subfile:``, ``data:``, + and ``gopher:``. Set to ``None`` to disable the whitelist entirely + (not recommended unless you are certain of the input source). Raises ------ @@ -349,6 +370,32 @@ class FFmpegPCMAudio(FFmpegAudio): The subprocess failed to be created. """ + @overload + def __init__( + self, + source: io.BufferedIOBase, + *, + executable: str = ..., + pipe: Literal[True] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: set[str] | None = ..., + ) -> None: ... + + @overload + def __init__( + self, + source: str, + *, + executable: str = ..., + pipe: Literal[False] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: set[str] | None = ..., + ) -> None: ... + def __init__( self, source: str | io.BufferedIOBase, @@ -358,6 +405,7 @@ def __init__( stderr: IO[bytes] | None = None, before_options: str | None = None, options: str | None = None, + protocol_whitelist: set[str] | None = DEFAULT_PROTOCOL_WHITELIST, ) -> None: args = [] subprocess_kwargs = { @@ -368,6 +416,9 @@ def __init__( if isinstance(before_options, str): args.extend(shlex.split(before_options)) + if protocol_whitelist is not None: + args.extend(["-protocol_whitelist", ",".join(protocol_whitelist)]) + args.append("-i") args.append("-" if pipe else source) @@ -430,6 +481,16 @@ class FFmpegOpusAudio(FFmpegAudio): The input that ffmpeg will take and convert to Opus bytes. If ``pipe`` is ``True`` then this is a file-like object that is passed to the stdin of ffmpeg. + + .. warning:: + + The ``source`` parameter is passed directly to your executable's ``-i`` flag and + interpreted through its full protocol machinery. This means it can accept + ``file://`` paths (local file read), ``http(s)://`` to internal services + (SSRF), ``concat:`` (multi-file read), and other dangerous schemes. + You should never pass attacker-controlled values (e.g. raw user input from a chat + command) to ``source`` without validation. + bitrate: :class:`int` The bitrate in kbps to encode the output to. Defaults to ``128``. codec: Optional[:class:`str`] @@ -456,6 +517,12 @@ class FFmpegOpusAudio(FFmpegAudio): Extra command line arguments to pass to ffmpeg before the ``-i`` flag. options: Optional[:class:`str`] Extra command line arguments to pass to ffmpeg after the ``-i`` flag. + protocol_whitelist: Optional[:class:`set[str]`] + A set of protocols that ffmpeg is allowed to use. + Defaults to ``{"file", "http", "https", "tcp", "tls", "crypto", "pipe", "fd", "cache"}``, which + blocks dangerous schemes such as ``concat:``, ``subfile:``, ``data:``, + and ``gopher:``. Set to ``None`` to disable the whitelist entirely + (not recommended unless you are certain of the input source). Raises ------ @@ -463,6 +530,36 @@ class FFmpegOpusAudio(FFmpegAudio): The subprocess failed to be created. """ + @overload + def __init__( + self, + source: io.BufferedIOBase, + *, + bitrate: int | None = None, + codec: str | None = None, + executable: str = ..., + pipe: Literal[True] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: set[str] | None = ..., + ) -> None: ... + + @overload + def __init__( + self, + source: str, + *, + bitrate: int | None = None, + codec: str | None = None, + executable: str = ..., + pipe: Literal[False] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: set[str] | None = ..., + ) -> None: ... + def __init__( self, source: str | io.BufferedIOBase, @@ -474,6 +571,7 @@ def __init__( stderr: IO[bytes] | None = None, before_options: str | None = None, options: str | None = None, + protocol_whitelist: set[str] | None = DEFAULT_PROTOCOL_WHITELIST, ) -> None: args = [] subprocess_kwargs = { @@ -484,6 +582,9 @@ def __init__( if isinstance(before_options, str): args.extend(shlex.split(before_options)) + if protocol_whitelist is not None: + args.extend(["-protocol_whitelist", ",".join(protocol_whitelist)]) + args.append("-i") args.append("-" if pipe else source) From 2cc8bdf880cec071812aace2519e5236f4091575 Mon Sep 17 00:00:00 2001 From: Paillat Date: Mon, 11 May 2026 14:45:55 +0200 Subject: [PATCH 2/7] Update discord/player.py Co-authored-by: Michael Signed-off-by: Paillat --- discord/player.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discord/player.py b/discord/player.py index 9d89eb1d7a..250079f4f0 100644 --- a/discord/player.py +++ b/discord/player.py @@ -357,7 +357,7 @@ class FFmpegPCMAudio(FFmpegAudio): Extra command line arguments to pass to ffmpeg before the ``-i`` flag. options: Optional[:class:`str`] Extra command line arguments to pass to ffmpeg after the ``-i`` flag. - protocol_whitelist: Optional[:class:`str`] + protocol_whitelist: Optional[:class:`set[str]`] A comma-separated list of protocols that ffmpeg is allowed to use. Defaults to ``"file,http,https,tcp,tls,crypto,pipe,fd,cache"``, which blocks dangerous schemes such as ``concat:``, ``subfile:``, ``data:``, From 095beb3335f5dd955e2c7c6f1e0f9b13ad6ebcca Mon Sep 17 00:00:00 2001 From: Paillat-dev Date: Mon, 8 Jun 2026 17:39:32 +0200 Subject: [PATCH 3/7] fix: Typing --- discord/player.py | 183 +++++++++++++++++++++++----------------------- 1 file changed, 93 insertions(+), 90 deletions(-) diff --git a/discord/player.py b/discord/player.py index 250079f4f0..56b7c877ec 100644 --- a/discord/player.py +++ b/discord/player.py @@ -39,7 +39,7 @@ import warnings from math import floor from typing import IO, TYPE_CHECKING, Any, Callable, Generic, Literal, TypeVar, overload - +from collections.abc import Set from .enums import SpeakingState from .errors import ClientException from .oggparse import OggStream @@ -52,7 +52,6 @@ from .voice import VoiceClient - AT = TypeVar("AT", bound="AudioSource") FT = TypeVar("FT", bound="FFmpegOpusAudio") @@ -152,12 +151,12 @@ class FFmpegAudio(AudioSource): BLOCKSIZE: int = io.DEFAULT_BUFFER_SIZE def __init__( - self, - source: str | io.BufferedIOBase, - *, - executable: str = "ffmpeg", - args: Any, - **subprocess_kwargs: Any, + self, + source: str | io.BufferedIOBase, + *, + executable: str = "ffmpeg", + args: Any, + **subprocess_kwargs: Any, ): piping_stdin = subprocess_kwargs.get("stdin") == subprocess.PIPE if piping_stdin and isinstance(source, str): @@ -309,7 +308,7 @@ def cleanup(self) -> None: self._process = self._stdout = self._stdin = self._stderr = MISSING -DEFAULT_PROTOCOL_WHITELIST: set[str] = frozenset( +DEFAULT_PROTOCOL_WHITELIST: Set[str] = frozenset( {"file", "http", "https", "tcp", "tls", "crypto", "pipe", "fd", "cache"} ) @@ -372,40 +371,42 @@ class FFmpegPCMAudio(FFmpegAudio): @overload def __init__( - self, - source: io.BufferedIOBase, - *, - executable: str = ..., - pipe: Literal[True] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: set[str] | None = ..., - ) -> None: ... + self, + source: io.BufferedIOBase, + *, + executable: str = ..., + pipe: Literal[True] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Set[str] | None = ..., + ) -> None: + ... @overload def __init__( - self, - source: str, - *, - executable: str = ..., - pipe: Literal[False] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: set[str] | None = ..., - ) -> None: ... + self, + source: str, + *, + executable: str = ..., + pipe: Literal[False] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Set[str] | None = ..., + ) -> None: + ... def __init__( - self, - source: str | io.BufferedIOBase, - *, - executable: str = "ffmpeg", - pipe: bool = False, - stderr: IO[bytes] | None = None, - before_options: str | None = None, - options: str | None = None, - protocol_whitelist: set[str] | None = DEFAULT_PROTOCOL_WHITELIST, + self, + source: str | io.BufferedIOBase, + *, + executable: str = "ffmpeg", + pipe: bool = False, + stderr: IO[bytes] | None = None, + before_options: str | None = None, + options: str | None = None, + protocol_whitelist: Set[str] | None = DEFAULT_PROTOCOL_WHITELIST, ) -> None: args = [] subprocess_kwargs = { @@ -532,46 +533,48 @@ class FFmpegOpusAudio(FFmpegAudio): @overload def __init__( - self, - source: io.BufferedIOBase, - *, - bitrate: int | None = None, - codec: str | None = None, - executable: str = ..., - pipe: Literal[True] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: set[str] | None = ..., - ) -> None: ... + self, + source: io.BufferedIOBase, + *, + bitrate: int | None = None, + codec: str | None = None, + executable: str = ..., + pipe: Literal[True] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Set[str] | None = ..., + ) -> None: + ... @overload def __init__( - self, - source: str, - *, - bitrate: int | None = None, - codec: str | None = None, - executable: str = ..., - pipe: Literal[False] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: set[str] | None = ..., - ) -> None: ... + self, + source: str, + *, + bitrate: int | None = None, + codec: str | None = None, + executable: str = ..., + pipe: Literal[False] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Set[str] | None = ..., + ) -> None: + ... def __init__( - self, - source: str | io.BufferedIOBase, - *, - bitrate: int | None = None, - codec: str | None = None, - executable: str = "ffmpeg", - pipe: bool = False, - stderr: IO[bytes] | None = None, - before_options: str | None = None, - options: str | None = None, - protocol_whitelist: set[str] | None = DEFAULT_PROTOCOL_WHITELIST, + self, + source: str | io.BufferedIOBase, + *, + bitrate: int | None = None, + codec: str | None = None, + executable: str = "ffmpeg", + pipe: bool = False, + stderr: IO[bytes] | None = None, + before_options: str | None = None, + options: str | None = None, + protocol_whitelist: Set[str] | None = DEFAULT_PROTOCOL_WHITELIST, ) -> None: args = [] subprocess_kwargs = { @@ -626,11 +629,11 @@ def __init__( @classmethod async def from_probe( - cls, - source: str, - *, - method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, - **kwargs: Any, + cls, + source: str, + *, + method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, + **kwargs: Any, ) -> Self: r"""|coro| @@ -693,11 +696,11 @@ def custom_probe(source, executable): @classmethod async def probe( - cls, - source: str, - *, - method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, - executable: str | None = None, + cls, + source: str, + *, + method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, + executable: str | None = None, ) -> tuple[str | None, int | None]: """|coro| @@ -779,7 +782,7 @@ async def probe( @staticmethod def _probe_codec_native( - source, executable: str = "ffmpeg" + source, executable: str = "ffmpeg" ) -> tuple[str | None, int | None]: exe = ( executable[:2] + "probe" @@ -812,7 +815,7 @@ def _probe_codec_native( @staticmethod def _probe_codec_fallback( - source, executable: str = "ffmpeg" + source, executable: str = "ffmpeg" ) -> tuple[str | None, int | None]: args = [executable, "-hide_banner", "-i", source] proc = subprocess.Popen( @@ -904,11 +907,11 @@ class AudioPlayer(threading.Thread): DELAY: float = OpusEncoder.FRAME_LENGTH / 1000.0 def __init__( - self, - source: AudioSource, - client: VoiceClient, - *, - after: Callable[[Exception | None], Any] | None = None, + self, + source: AudioSource, + client: VoiceClient, + *, + after: Callable[[Exception | None], Any] | None = None, ) -> None: super().__init__(daemon=True, name=f"audio-player:{id(self):#x}") self.source: AudioSource = source From d612951be4cdf37b814725f6fd2103f3aea826fa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 15:41:04 +0000 Subject: [PATCH 4/7] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/player.py | 181 +++++++++++++++++++++++----------------------- 1 file changed, 89 insertions(+), 92 deletions(-) diff --git a/discord/player.py b/discord/player.py index 56b7c877ec..31e99bd60b 100644 --- a/discord/player.py +++ b/discord/player.py @@ -37,9 +37,10 @@ import threading import time import warnings +from collections.abc import Set from math import floor from typing import IO, TYPE_CHECKING, Any, Callable, Generic, Literal, TypeVar, overload -from collections.abc import Set + from .enums import SpeakingState from .errors import ClientException from .oggparse import OggStream @@ -151,12 +152,12 @@ class FFmpegAudio(AudioSource): BLOCKSIZE: int = io.DEFAULT_BUFFER_SIZE def __init__( - self, - source: str | io.BufferedIOBase, - *, - executable: str = "ffmpeg", - args: Any, - **subprocess_kwargs: Any, + self, + source: str | io.BufferedIOBase, + *, + executable: str = "ffmpeg", + args: Any, + **subprocess_kwargs: Any, ): piping_stdin = subprocess_kwargs.get("stdin") == subprocess.PIPE if piping_stdin and isinstance(source, str): @@ -371,42 +372,40 @@ class FFmpegPCMAudio(FFmpegAudio): @overload def __init__( - self, - source: io.BufferedIOBase, - *, - executable: str = ..., - pipe: Literal[True] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: Set[str] | None = ..., - ) -> None: - ... + self, + source: io.BufferedIOBase, + *, + executable: str = ..., + pipe: Literal[True] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Set[str] | None = ..., + ) -> None: ... @overload def __init__( - self, - source: str, - *, - executable: str = ..., - pipe: Literal[False] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: Set[str] | None = ..., - ) -> None: - ... + self, + source: str, + *, + executable: str = ..., + pipe: Literal[False] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Set[str] | None = ..., + ) -> None: ... def __init__( - self, - source: str | io.BufferedIOBase, - *, - executable: str = "ffmpeg", - pipe: bool = False, - stderr: IO[bytes] | None = None, - before_options: str | None = None, - options: str | None = None, - protocol_whitelist: Set[str] | None = DEFAULT_PROTOCOL_WHITELIST, + self, + source: str | io.BufferedIOBase, + *, + executable: str = "ffmpeg", + pipe: bool = False, + stderr: IO[bytes] | None = None, + before_options: str | None = None, + options: str | None = None, + protocol_whitelist: Set[str] | None = DEFAULT_PROTOCOL_WHITELIST, ) -> None: args = [] subprocess_kwargs = { @@ -533,48 +532,46 @@ class FFmpegOpusAudio(FFmpegAudio): @overload def __init__( - self, - source: io.BufferedIOBase, - *, - bitrate: int | None = None, - codec: str | None = None, - executable: str = ..., - pipe: Literal[True] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: Set[str] | None = ..., - ) -> None: - ... + self, + source: io.BufferedIOBase, + *, + bitrate: int | None = None, + codec: str | None = None, + executable: str = ..., + pipe: Literal[True] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Set[str] | None = ..., + ) -> None: ... @overload def __init__( - self, - source: str, - *, - bitrate: int | None = None, - codec: str | None = None, - executable: str = ..., - pipe: Literal[False] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: Set[str] | None = ..., - ) -> None: - ... + self, + source: str, + *, + bitrate: int | None = None, + codec: str | None = None, + executable: str = ..., + pipe: Literal[False] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Set[str] | None = ..., + ) -> None: ... def __init__( - self, - source: str | io.BufferedIOBase, - *, - bitrate: int | None = None, - codec: str | None = None, - executable: str = "ffmpeg", - pipe: bool = False, - stderr: IO[bytes] | None = None, - before_options: str | None = None, - options: str | None = None, - protocol_whitelist: Set[str] | None = DEFAULT_PROTOCOL_WHITELIST, + self, + source: str | io.BufferedIOBase, + *, + bitrate: int | None = None, + codec: str | None = None, + executable: str = "ffmpeg", + pipe: bool = False, + stderr: IO[bytes] | None = None, + before_options: str | None = None, + options: str | None = None, + protocol_whitelist: Set[str] | None = DEFAULT_PROTOCOL_WHITELIST, ) -> None: args = [] subprocess_kwargs = { @@ -629,11 +626,11 @@ def __init__( @classmethod async def from_probe( - cls, - source: str, - *, - method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, - **kwargs: Any, + cls, + source: str, + *, + method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, + **kwargs: Any, ) -> Self: r"""|coro| @@ -696,11 +693,11 @@ def custom_probe(source, executable): @classmethod async def probe( - cls, - source: str, - *, - method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, - executable: str | None = None, + cls, + source: str, + *, + method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, + executable: str | None = None, ) -> tuple[str | None, int | None]: """|coro| @@ -782,7 +779,7 @@ async def probe( @staticmethod def _probe_codec_native( - source, executable: str = "ffmpeg" + source, executable: str = "ffmpeg" ) -> tuple[str | None, int | None]: exe = ( executable[:2] + "probe" @@ -815,7 +812,7 @@ def _probe_codec_native( @staticmethod def _probe_codec_fallback( - source, executable: str = "ffmpeg" + source, executable: str = "ffmpeg" ) -> tuple[str | None, int | None]: args = [executable, "-hide_banner", "-i", source] proc = subprocess.Popen( @@ -907,11 +904,11 @@ class AudioPlayer(threading.Thread): DELAY: float = OpusEncoder.FRAME_LENGTH / 1000.0 def __init__( - self, - source: AudioSource, - client: VoiceClient, - *, - after: Callable[[Exception | None], Any] | None = None, + self, + source: AudioSource, + client: VoiceClient, + *, + after: Callable[[Exception | None], Any] | None = None, ) -> None: super().__init__(daemon=True, name=f"audio-player:{id(self):#x}") self.source: AudioSource = source From 3de596ef4a95ff52e6e0dee9eb4e8e7573f74fa5 Mon Sep 17 00:00:00 2001 From: Paillat-dev Date: Mon, 8 Jun 2026 18:31:45 +0200 Subject: [PATCH 5/7] chore: Add warning when duplicate and use tuple --- discord/player.py | 210 +++++++++++++++++++++++++--------------------- 1 file changed, 113 insertions(+), 97 deletions(-) diff --git a/discord/player.py b/discord/player.py index 31e99bd60b..ee7ee9e1f5 100644 --- a/discord/player.py +++ b/discord/player.py @@ -37,7 +37,7 @@ import threading import time import warnings -from collections.abc import Set +from collections.abc import Sequence from math import floor from typing import IO, TYPE_CHECKING, Any, Callable, Generic, Literal, TypeVar, overload @@ -152,12 +152,12 @@ class FFmpegAudio(AudioSource): BLOCKSIZE: int = io.DEFAULT_BUFFER_SIZE def __init__( - self, - source: str | io.BufferedIOBase, - *, - executable: str = "ffmpeg", - args: Any, - **subprocess_kwargs: Any, + self, + source: str | io.BufferedIOBase, + *, + executable: str = "ffmpeg", + args: Any, + **subprocess_kwargs: Any, ): piping_stdin = subprocess_kwargs.get("stdin") == subprocess.PIPE if piping_stdin and isinstance(source, str): @@ -309,9 +309,7 @@ def cleanup(self) -> None: self._process = self._stdout = self._stdin = self._stderr = MISSING -DEFAULT_PROTOCOL_WHITELIST: Set[str] = frozenset( - {"file", "http", "https", "tcp", "tls", "crypto", "pipe", "fd", "cache"} -) +DEFAULT_PROTOCOL_WHITELIST: Sequence[str] = ("file", "http", "https", "tcp", "tls", "crypto", "pipe", "fd", "cache") class FFmpegPCMAudio(FFmpegAudio): @@ -357,8 +355,8 @@ class FFmpegPCMAudio(FFmpegAudio): Extra command line arguments to pass to ffmpeg before the ``-i`` flag. options: Optional[:class:`str`] Extra command line arguments to pass to ffmpeg after the ``-i`` flag. - protocol_whitelist: Optional[:class:`set[str]`] - A comma-separated list of protocols that ffmpeg is allowed to use. + protocol_whitelist: Optional[:class:`abc.Sequence[str]`] + A sequence of protocols that ffmpeg is allowed to use. Defaults to ``"file,http,https,tcp,tls,crypto,pipe,fd,cache"``, which blocks dangerous schemes such as ``concat:``, ``subfile:``, ``data:``, and ``gopher:``. Set to ``None`` to disable the whitelist entirely @@ -372,40 +370,42 @@ class FFmpegPCMAudio(FFmpegAudio): @overload def __init__( - self, - source: io.BufferedIOBase, - *, - executable: str = ..., - pipe: Literal[True] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: Set[str] | None = ..., - ) -> None: ... + self, + source: io.BufferedIOBase, + *, + executable: str = ..., + pipe: Literal[True] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Sequence[str] | None = ..., + ) -> None: + ... @overload def __init__( - self, - source: str, - *, - executable: str = ..., - pipe: Literal[False] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: Set[str] | None = ..., - ) -> None: ... + self, + source: str, + *, + executable: str = ..., + pipe: Literal[False] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Sequence[str] | None = ..., + ) -> None: + ... def __init__( - self, - source: str | io.BufferedIOBase, - *, - executable: str = "ffmpeg", - pipe: bool = False, - stderr: IO[bytes] | None = None, - before_options: str | None = None, - options: str | None = None, - protocol_whitelist: Set[str] | None = DEFAULT_PROTOCOL_WHITELIST, + self, + source: str | io.BufferedIOBase, + *, + executable: str = "ffmpeg", + pipe: bool = False, + stderr: IO[bytes] | None = None, + before_options: str | None = None, + options: str | None = None, + protocol_whitelist: Sequence[str] | None = DEFAULT_PROTOCOL_WHITELIST, ) -> None: args = [] subprocess_kwargs = { @@ -414,7 +414,14 @@ def __init__( } if isinstance(before_options, str): - args.extend(shlex.split(before_options)) + user_args = shlex.split(before_options) + if "-protocol_whitelist" in user_args and protocol_whitelist is not None: + protocol_whitelist = None + warnings.warn( + "the protocol_whitelist argument is being ignored because -protocol_whitelist was found in before_options.", + UserWarning, + ) + args.extend(user_args) if protocol_whitelist is not None: args.extend(["-protocol_whitelist", ",".join(protocol_whitelist)]) @@ -517,8 +524,8 @@ class FFmpegOpusAudio(FFmpegAudio): Extra command line arguments to pass to ffmpeg before the ``-i`` flag. options: Optional[:class:`str`] Extra command line arguments to pass to ffmpeg after the ``-i`` flag. - protocol_whitelist: Optional[:class:`set[str]`] - A set of protocols that ffmpeg is allowed to use. + protocol_whitelist: Optional[:class:`abc.Sequence[str]`] + A sequence of protocols that ffmpeg is allowed to use. Defaults to ``{"file", "http", "https", "tcp", "tls", "crypto", "pipe", "fd", "cache"}``, which blocks dangerous schemes such as ``concat:``, ``subfile:``, ``data:``, and ``gopher:``. Set to ``None`` to disable the whitelist entirely @@ -532,46 +539,48 @@ class FFmpegOpusAudio(FFmpegAudio): @overload def __init__( - self, - source: io.BufferedIOBase, - *, - bitrate: int | None = None, - codec: str | None = None, - executable: str = ..., - pipe: Literal[True] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: Set[str] | None = ..., - ) -> None: ... + self, + source: io.BufferedIOBase, + *, + bitrate: int | None = None, + codec: str | None = None, + executable: str = ..., + pipe: Literal[True] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Sequence[str] | None = ..., + ) -> None: + ... @overload def __init__( - self, - source: str, - *, - bitrate: int | None = None, - codec: str | None = None, - executable: str = ..., - pipe: Literal[False] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: Set[str] | None = ..., - ) -> None: ... + self, + source: str, + *, + bitrate: int | None = None, + codec: str | None = None, + executable: str = ..., + pipe: Literal[False] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Sequence[str] | None = ..., + ) -> None: + ... def __init__( - self, - source: str | io.BufferedIOBase, - *, - bitrate: int | None = None, - codec: str | None = None, - executable: str = "ffmpeg", - pipe: bool = False, - stderr: IO[bytes] | None = None, - before_options: str | None = None, - options: str | None = None, - protocol_whitelist: Set[str] | None = DEFAULT_PROTOCOL_WHITELIST, + self, + source: str | io.BufferedIOBase, + *, + bitrate: int | None = None, + codec: str | None = None, + executable: str = "ffmpeg", + pipe: bool = False, + stderr: IO[bytes] | None = None, + before_options: str | None = None, + options: str | None = None, + protocol_whitelist: Sequence[str] | None = DEFAULT_PROTOCOL_WHITELIST, ) -> None: args = [] subprocess_kwargs = { @@ -580,7 +589,14 @@ def __init__( } if isinstance(before_options, str): - args.extend(shlex.split(before_options)) + user_args = shlex.split(before_options) + if "-protocol_whitelist" in user_args and protocol_whitelist is not None: + protocol_whitelist = None + warnings.warn( + "the protocol_whitelist argument is being ignored because -protocol_whitelist was found in before_options.", + UserWarning, + ) + args.extend(user_args) if protocol_whitelist is not None: args.extend(["-protocol_whitelist", ",".join(protocol_whitelist)]) @@ -626,11 +642,11 @@ def __init__( @classmethod async def from_probe( - cls, - source: str, - *, - method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, - **kwargs: Any, + cls, + source: str, + *, + method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, + **kwargs: Any, ) -> Self: r"""|coro| @@ -693,11 +709,11 @@ def custom_probe(source, executable): @classmethod async def probe( - cls, - source: str, - *, - method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, - executable: str | None = None, + cls, + source: str, + *, + method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, + executable: str | None = None, ) -> tuple[str | None, int | None]: """|coro| @@ -779,7 +795,7 @@ async def probe( @staticmethod def _probe_codec_native( - source, executable: str = "ffmpeg" + source, executable: str = "ffmpeg" ) -> tuple[str | None, int | None]: exe = ( executable[:2] + "probe" @@ -812,7 +828,7 @@ def _probe_codec_native( @staticmethod def _probe_codec_fallback( - source, executable: str = "ffmpeg" + source, executable: str = "ffmpeg" ) -> tuple[str | None, int | None]: args = [executable, "-hide_banner", "-i", source] proc = subprocess.Popen( @@ -904,11 +920,11 @@ class AudioPlayer(threading.Thread): DELAY: float = OpusEncoder.FRAME_LENGTH / 1000.0 def __init__( - self, - source: AudioSource, - client: VoiceClient, - *, - after: Callable[[Exception | None], Any] | None = None, + self, + source: AudioSource, + client: VoiceClient, + *, + after: Callable[[Exception | None], Any] | None = None, ) -> None: super().__init__(daemon=True, name=f"audio-player:{id(self):#x}") self.source: AudioSource = source From 87f23a9052434cf448d32f36acaf15229caed78b Mon Sep 17 00:00:00 2001 From: Paillat-dev Date: Mon, 8 Jun 2026 18:34:47 +0200 Subject: [PATCH 6/7] chore: Dont ignore when protocol_whitelist is NOne --- discord/player.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/discord/player.py b/discord/player.py index ee7ee9e1f5..19db1c1f4f 100644 --- a/discord/player.py +++ b/discord/player.py @@ -415,7 +415,7 @@ def __init__( if isinstance(before_options, str): user_args = shlex.split(before_options) - if "-protocol_whitelist" in user_args and protocol_whitelist is not None: + if "-protocol_whitelist" in user_args: protocol_whitelist = None warnings.warn( "the protocol_whitelist argument is being ignored because -protocol_whitelist was found in before_options.", @@ -590,7 +590,7 @@ def __init__( if isinstance(before_options, str): user_args = shlex.split(before_options) - if "-protocol_whitelist" in user_args and protocol_whitelist is not None: + if "-protocol_whitelist" in user_args: protocol_whitelist = None warnings.warn( "the protocol_whitelist argument is being ignored because -protocol_whitelist was found in before_options.", From 7358aee109084011897afa416d3702f0c1989bd0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2026 16:36:06 +0000 Subject: [PATCH 7/7] style(pre-commit): auto fixes from pre-commit.com hooks --- discord/player.py | 190 ++++++++++++++++++++++++---------------------- 1 file changed, 98 insertions(+), 92 deletions(-) diff --git a/discord/player.py b/discord/player.py index 19db1c1f4f..5fb8c77c49 100644 --- a/discord/player.py +++ b/discord/player.py @@ -152,12 +152,12 @@ class FFmpegAudio(AudioSource): BLOCKSIZE: int = io.DEFAULT_BUFFER_SIZE def __init__( - self, - source: str | io.BufferedIOBase, - *, - executable: str = "ffmpeg", - args: Any, - **subprocess_kwargs: Any, + self, + source: str | io.BufferedIOBase, + *, + executable: str = "ffmpeg", + args: Any, + **subprocess_kwargs: Any, ): piping_stdin = subprocess_kwargs.get("stdin") == subprocess.PIPE if piping_stdin and isinstance(source, str): @@ -309,7 +309,17 @@ def cleanup(self) -> None: self._process = self._stdout = self._stdin = self._stderr = MISSING -DEFAULT_PROTOCOL_WHITELIST: Sequence[str] = ("file", "http", "https", "tcp", "tls", "crypto", "pipe", "fd", "cache") +DEFAULT_PROTOCOL_WHITELIST: Sequence[str] = ( + "file", + "http", + "https", + "tcp", + "tls", + "crypto", + "pipe", + "fd", + "cache", +) class FFmpegPCMAudio(FFmpegAudio): @@ -370,42 +380,40 @@ class FFmpegPCMAudio(FFmpegAudio): @overload def __init__( - self, - source: io.BufferedIOBase, - *, - executable: str = ..., - pipe: Literal[True] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: Sequence[str] | None = ..., - ) -> None: - ... + self, + source: io.BufferedIOBase, + *, + executable: str = ..., + pipe: Literal[True] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Sequence[str] | None = ..., + ) -> None: ... @overload def __init__( - self, - source: str, - *, - executable: str = ..., - pipe: Literal[False] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: Sequence[str] | None = ..., - ) -> None: - ... + self, + source: str, + *, + executable: str = ..., + pipe: Literal[False] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Sequence[str] | None = ..., + ) -> None: ... def __init__( - self, - source: str | io.BufferedIOBase, - *, - executable: str = "ffmpeg", - pipe: bool = False, - stderr: IO[bytes] | None = None, - before_options: str | None = None, - options: str | None = None, - protocol_whitelist: Sequence[str] | None = DEFAULT_PROTOCOL_WHITELIST, + self, + source: str | io.BufferedIOBase, + *, + executable: str = "ffmpeg", + pipe: bool = False, + stderr: IO[bytes] | None = None, + before_options: str | None = None, + options: str | None = None, + protocol_whitelist: Sequence[str] | None = DEFAULT_PROTOCOL_WHITELIST, ) -> None: args = [] subprocess_kwargs = { @@ -539,48 +547,46 @@ class FFmpegOpusAudio(FFmpegAudio): @overload def __init__( - self, - source: io.BufferedIOBase, - *, - bitrate: int | None = None, - codec: str | None = None, - executable: str = ..., - pipe: Literal[True] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: Sequence[str] | None = ..., - ) -> None: - ... + self, + source: io.BufferedIOBase, + *, + bitrate: int | None = None, + codec: str | None = None, + executable: str = ..., + pipe: Literal[True] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Sequence[str] | None = ..., + ) -> None: ... @overload def __init__( - self, - source: str, - *, - bitrate: int | None = None, - codec: str | None = None, - executable: str = ..., - pipe: Literal[False] = ..., - stderr: IO[bytes] | None = ..., - before_options: str | None = ..., - options: str | None = ..., - protocol_whitelist: Sequence[str] | None = ..., - ) -> None: - ... + self, + source: str, + *, + bitrate: int | None = None, + codec: str | None = None, + executable: str = ..., + pipe: Literal[False] = ..., + stderr: IO[bytes] | None = ..., + before_options: str | None = ..., + options: str | None = ..., + protocol_whitelist: Sequence[str] | None = ..., + ) -> None: ... def __init__( - self, - source: str | io.BufferedIOBase, - *, - bitrate: int | None = None, - codec: str | None = None, - executable: str = "ffmpeg", - pipe: bool = False, - stderr: IO[bytes] | None = None, - before_options: str | None = None, - options: str | None = None, - protocol_whitelist: Sequence[str] | None = DEFAULT_PROTOCOL_WHITELIST, + self, + source: str | io.BufferedIOBase, + *, + bitrate: int | None = None, + codec: str | None = None, + executable: str = "ffmpeg", + pipe: bool = False, + stderr: IO[bytes] | None = None, + before_options: str | None = None, + options: str | None = None, + protocol_whitelist: Sequence[str] | None = DEFAULT_PROTOCOL_WHITELIST, ) -> None: args = [] subprocess_kwargs = { @@ -642,11 +648,11 @@ def __init__( @classmethod async def from_probe( - cls, - source: str, - *, - method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, - **kwargs: Any, + cls, + source: str, + *, + method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, + **kwargs: Any, ) -> Self: r"""|coro| @@ -709,11 +715,11 @@ def custom_probe(source, executable): @classmethod async def probe( - cls, - source: str, - *, - method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, - executable: str | None = None, + cls, + source: str, + *, + method: str | Callable[[str, str], tuple[str | None, int | None]] | None = None, + executable: str | None = None, ) -> tuple[str | None, int | None]: """|coro| @@ -795,7 +801,7 @@ async def probe( @staticmethod def _probe_codec_native( - source, executable: str = "ffmpeg" + source, executable: str = "ffmpeg" ) -> tuple[str | None, int | None]: exe = ( executable[:2] + "probe" @@ -828,7 +834,7 @@ def _probe_codec_native( @staticmethod def _probe_codec_fallback( - source, executable: str = "ffmpeg" + source, executable: str = "ffmpeg" ) -> tuple[str | None, int | None]: args = [executable, "-hide_banner", "-i", source] proc = subprocess.Popen( @@ -920,11 +926,11 @@ class AudioPlayer(threading.Thread): DELAY: float = OpusEncoder.FRAME_LENGTH / 1000.0 def __init__( - self, - source: AudioSource, - client: VoiceClient, - *, - after: Callable[[Exception | None], Any] | None = None, + self, + source: AudioSource, + client: VoiceClient, + *, + after: Callable[[Exception | None], Any] | None = None, ) -> None: super().__init__(daemon=True, name=f"audio-player:{id(self):#x}") self.source: AudioSource = source