|
22 | 22 |
|
23 | 23 | yaml = YAML() |
24 | 24 |
|
| 25 | +# Check if arbitrary exec sources are allowed (defaults to False for security) |
| 26 | +ALLOW_ARBITRARY_EXEC = os.environ.get( |
| 27 | + "GO2RTC_ALLOW_ARBITRARY_EXEC", "false" |
| 28 | +).lower() in ("true", "1", "yes") |
| 29 | + |
| 30 | +# check for the add-on options file |
| 31 | +if not ALLOW_ARBITRARY_EXEC and os.path.isfile("/data/options.json"): |
| 32 | + with open("/data/options.json") as f: |
| 33 | + raw_options = f.read() |
| 34 | + options = json.loads(raw_options) |
| 35 | + addon_value = options.get("go2rtc_allow_arbitrary_exec", False) |
| 36 | + if isinstance(addon_value, bool): |
| 37 | + ALLOW_ARBITRARY_EXEC = addon_value |
| 38 | + elif isinstance(addon_value, str): |
| 39 | + ALLOW_ARBITRARY_EXEC = addon_value.lower() in ("true", "1", "yes") |
| 40 | + |
25 | 41 | FRIGATE_ENV_VARS = {k: v for k, v in os.environ.items() if k.startswith("FRIGATE_")} |
26 | 42 | # read docker secret files as env vars too |
27 | 43 | if os.path.isdir("/run/secrets"): |
|
109 | 125 | elif go2rtc_config["ffmpeg"].get("rtsp") is None: |
110 | 126 | go2rtc_config["ffmpeg"]["rtsp"] = rtsp_args |
111 | 127 |
|
112 | | -for name in go2rtc_config.get("streams", {}): |
| 128 | + |
| 129 | +def is_restricted_source(stream_source: str) -> bool: |
| 130 | + """Check if a stream source is restricted (echo, expr, or exec).""" |
| 131 | + return stream_source.strip().startswith(("echo:", "expr:", "exec:")) |
| 132 | + |
| 133 | + |
| 134 | +for name in list(go2rtc_config.get("streams", {})): |
113 | 135 | stream = go2rtc_config["streams"][name] |
114 | 136 |
|
115 | 137 | if isinstance(stream, str): |
116 | 138 | try: |
117 | | - go2rtc_config["streams"][name] = go2rtc_config["streams"][name].format( |
118 | | - **FRIGATE_ENV_VARS |
119 | | - ) |
| 139 | + formatted_stream = stream.format(**FRIGATE_ENV_VARS) |
| 140 | + if not ALLOW_ARBITRARY_EXEC and is_restricted_source(formatted_stream): |
| 141 | + print( |
| 142 | + f"[ERROR] Stream '{name}' uses a restricted source (echo/expr/exec) which is disabled by default for security. " |
| 143 | + f"Set GO2RTC_ALLOW_ARBITRARY_EXEC=true to enable arbitrary exec sources." |
| 144 | + ) |
| 145 | + del go2rtc_config["streams"][name] |
| 146 | + continue |
| 147 | + go2rtc_config["streams"][name] = formatted_stream |
120 | 148 | except KeyError as e: |
121 | 149 | print( |
122 | 150 | "[ERROR] Invalid substitution found, see https://docs.frigate.video/configuration/restream#advanced-restream-configurations for more info." |
123 | 151 | ) |
124 | 152 | sys.exit(e) |
125 | 153 |
|
126 | 154 | elif isinstance(stream, list): |
127 | | - for i, stream in enumerate(stream): |
| 155 | + filtered_streams = [] |
| 156 | + for i, stream_item in enumerate(stream): |
128 | 157 | try: |
129 | | - go2rtc_config["streams"][name][i] = stream.format(**FRIGATE_ENV_VARS) |
| 158 | + formatted_stream = stream_item.format(**FRIGATE_ENV_VARS) |
| 159 | + if not ALLOW_ARBITRARY_EXEC and is_restricted_source(formatted_stream): |
| 160 | + print( |
| 161 | + f"[ERROR] Stream '{name}' item {i + 1} uses a restricted source (echo/expr/exec) which is disabled by default for security. " |
| 162 | + f"Set GO2RTC_ALLOW_ARBITRARY_EXEC=true to enable arbitrary exec sources." |
| 163 | + ) |
| 164 | + continue |
| 165 | + |
| 166 | + filtered_streams.append(formatted_stream) |
130 | 167 | except KeyError as e: |
131 | 168 | print( |
132 | 169 | "[ERROR] Invalid substitution found, see https://docs.frigate.video/configuration/restream#advanced-restream-configurations for more info." |
133 | 170 | ) |
134 | 171 | sys.exit(e) |
135 | 172 |
|
| 173 | + if filtered_streams: |
| 174 | + go2rtc_config["streams"][name] = filtered_streams |
| 175 | + else: |
| 176 | + print( |
| 177 | + f"[ERROR] Stream '{name}' was removed because all sources were restricted (echo/expr/exec). " |
| 178 | + f"Set GO2RTC_ALLOW_ARBITRARY_EXEC=true to enable arbitrary exec sources." |
| 179 | + ) |
| 180 | + del go2rtc_config["streams"][name] |
| 181 | + |
136 | 182 | # add birdseye restream stream if enabled |
137 | 183 | if config.get("birdseye", {}).get("restream", False): |
138 | 184 | birdseye: dict[str, Any] = config.get("birdseye") |
|
0 commit comments