3838import time
3939import warnings
4040from math import floor
41- from typing import IO , TYPE_CHECKING , Any , Callable , Generic , TypeVar
41+ from typing import IO , TYPE_CHECKING , Any , Callable , Generic , Literal , TypeVar , overload
4242
4343from .enums import SpeakingState
4444from .errors import ClientException
@@ -309,6 +309,11 @@ def cleanup(self) -> None:
309309 self ._process = self ._stdout = self ._stdin = self ._stderr = MISSING
310310
311311
312+ DEFAULT_PROTOCOL_WHITELIST : set [str ] = frozenset (
313+ {"file" , "http" , "https" , "tcp" , "tls" , "crypto" , "pipe" , "fd" , "cache" }
314+ )
315+
316+
312317class FFmpegPCMAudio (FFmpegAudio ):
313318 """An audio source from FFmpeg (or AVConv).
314319
@@ -325,6 +330,16 @@ class FFmpegPCMAudio(FFmpegAudio):
325330 The input that ffmpeg will take and convert to PCM bytes.
326331 If ``pipe`` is ``True`` then this is a file-like object that is
327332 passed to the stdin of ffmpeg.
333+
334+ .. warning::
335+
336+ The ``source`` parameter is passed directly to your executable's ``-i`` flag and
337+ interpreted through its full protocol machinery. This means it can accept
338+ ``file://`` paths (local file read), ``http(s)://`` to internal services
339+ (SSRF), ``concat:`` (multi-file read), and other dangerous schemes.
340+ You should never pass attacker-controlled values (e.g. raw user input from a chat
341+ command) to ``source`` without validation.
342+
328343 executable: :class:`str`
329344 The executable name (and path) to use. Defaults to ``ffmpeg``.
330345
@@ -342,13 +357,45 @@ class FFmpegPCMAudio(FFmpegAudio):
342357 Extra command line arguments to pass to ffmpeg before the ``-i`` flag.
343358 options: Optional[:class:`str`]
344359 Extra command line arguments to pass to ffmpeg after the ``-i`` flag.
360+ protocol_whitelist: Optional[:class:`str`]
361+ A comma-separated list of protocols that ffmpeg is allowed to use.
362+ Defaults to ``"file,http,https,tcp,tls,crypto,pipe,fd,cache"``, which
363+ blocks dangerous schemes such as ``concat:``, ``subfile:``, ``data:``,
364+ and ``gopher:``. Set to ``None`` to disable the whitelist entirely
365+ (not recommended unless you are certain of the input source).
345366
346367 Raises
347368 ------
348369 ClientException
349370 The subprocess failed to be created.
350371 """
351372
373+ @overload
374+ def __init__ (
375+ self ,
376+ source : io .BufferedIOBase ,
377+ * ,
378+ executable : str = ...,
379+ pipe : Literal [True ] = ...,
380+ stderr : IO [bytes ] | None = ...,
381+ before_options : str | None = ...,
382+ options : str | None = ...,
383+ protocol_whitelist : set [str ] | None = ...,
384+ ) -> None : ...
385+
386+ @overload
387+ def __init__ (
388+ self ,
389+ source : str ,
390+ * ,
391+ executable : str = ...,
392+ pipe : Literal [False ] = ...,
393+ stderr : IO [bytes ] | None = ...,
394+ before_options : str | None = ...,
395+ options : str | None = ...,
396+ protocol_whitelist : set [str ] | None = ...,
397+ ) -> None : ...
398+
352399 def __init__ (
353400 self ,
354401 source : str | io .BufferedIOBase ,
@@ -358,6 +405,7 @@ def __init__(
358405 stderr : IO [bytes ] | None = None ,
359406 before_options : str | None = None ,
360407 options : str | None = None ,
408+ protocol_whitelist : set [str ] | None = DEFAULT_PROTOCOL_WHITELIST ,
361409 ) -> None :
362410 args = []
363411 subprocess_kwargs = {
@@ -368,6 +416,9 @@ def __init__(
368416 if isinstance (before_options , str ):
369417 args .extend (shlex .split (before_options ))
370418
419+ if protocol_whitelist is not None :
420+ args .extend (["-protocol_whitelist" , "," .join (protocol_whitelist )])
421+
371422 args .append ("-i" )
372423 args .append ("-" if pipe else source )
373424
@@ -430,6 +481,16 @@ class FFmpegOpusAudio(FFmpegAudio):
430481 The input that ffmpeg will take and convert to Opus bytes.
431482 If ``pipe`` is ``True`` then this is a file-like object that is
432483 passed to the stdin of ffmpeg.
484+
485+ .. warning::
486+
487+ The ``source`` parameter is passed directly to your executable's ``-i`` flag and
488+ interpreted through its full protocol machinery. This means it can accept
489+ ``file://`` paths (local file read), ``http(s)://`` to internal services
490+ (SSRF), ``concat:`` (multi-file read), and other dangerous schemes.
491+ You should never pass attacker-controlled values (e.g. raw user input from a chat
492+ command) to ``source`` without validation.
493+
433494 bitrate: :class:`int`
434495 The bitrate in kbps to encode the output to. Defaults to ``128``.
435496 codec: Optional[:class:`str`]
@@ -456,13 +517,49 @@ class FFmpegOpusAudio(FFmpegAudio):
456517 Extra command line arguments to pass to ffmpeg before the ``-i`` flag.
457518 options: Optional[:class:`str`]
458519 Extra command line arguments to pass to ffmpeg after the ``-i`` flag.
520+ protocol_whitelist: Optional[:class:`set[str]`]
521+ A set of protocols that ffmpeg is allowed to use.
522+ Defaults to ``{"file", "http", "https", "tcp", "tls", "crypto", "pipe", "fd", "cache"}``, which
523+ blocks dangerous schemes such as ``concat:``, ``subfile:``, ``data:``,
524+ and ``gopher:``. Set to ``None`` to disable the whitelist entirely
525+ (not recommended unless you are certain of the input source).
459526
460527 Raises
461528 ------
462529 ClientException
463530 The subprocess failed to be created.
464531 """
465532
533+ @overload
534+ def __init__ (
535+ self ,
536+ source : io .BufferedIOBase ,
537+ * ,
538+ bitrate : int | None = None ,
539+ codec : str | None = None ,
540+ executable : str = ...,
541+ pipe : Literal [True ] = ...,
542+ stderr : IO [bytes ] | None = ...,
543+ before_options : str | None = ...,
544+ options : str | None = ...,
545+ protocol_whitelist : set [str ] | None = ...,
546+ ) -> None : ...
547+
548+ @overload
549+ def __init__ (
550+ self ,
551+ source : str ,
552+ * ,
553+ bitrate : int | None = None ,
554+ codec : str | None = None ,
555+ executable : str = ...,
556+ pipe : Literal [False ] = ...,
557+ stderr : IO [bytes ] | None = ...,
558+ before_options : str | None = ...,
559+ options : str | None = ...,
560+ protocol_whitelist : set [str ] | None = ...,
561+ ) -> None : ...
562+
466563 def __init__ (
467564 self ,
468565 source : str | io .BufferedIOBase ,
@@ -474,6 +571,7 @@ def __init__(
474571 stderr : IO [bytes ] | None = None ,
475572 before_options : str | None = None ,
476573 options : str | None = None ,
574+ protocol_whitelist : set [str ] | None = DEFAULT_PROTOCOL_WHITELIST ,
477575 ) -> None :
478576 args = []
479577 subprocess_kwargs = {
@@ -484,6 +582,9 @@ def __init__(
484582 if isinstance (before_options , str ):
485583 args .extend (shlex .split (before_options ))
486584
585+ if protocol_whitelist is not None :
586+ args .extend (["-protocol_whitelist" , "," .join (protocol_whitelist )])
587+
487588 args .append ("-i" )
488589 args .append ("-" if pipe else source )
489590
0 commit comments