security: harden code against command injection and path traversal

Core changes:

* enforce HTTPS URLs and remove shell usage in generate_release.py
* replace os.system calls with subprocess across the codebase
* validate external inputs (playlist names, video IDs)

Improvements and fixes:

* settings.py: fix typo (node.lineno → line_number); use isinstance() over type()
* youtube/get_app_version: improve git detection using subprocess.DEVNULL
* youtube/util.py: add cleanup helpers; use shutil.which for binary resolution

YouTube modules:

* watch.py: detect and flag HLS streams; remove unused audio_track_sources
* comments.py: return early when comments are disabled; add error handling
* local_playlist.py: validate playlist names to prevent path traversal
* subscriptions.py: replace asserts with proper error handling; validate video IDs

Cleanup:

* remove unused imports across modules (playlist, search, channel)
* reorganize package imports in youtube/**init**.py
* simplify test imports and fix cleanup_func in tests

Tests:

* tests/test_shorts.py: simplify imports
* tests/test_util.py: fix cleanup_func definition
This commit is contained in:
2026-04-20 00:39:35 -05:00
parent 155bd4df49
commit d6190a2d0b
16 changed files with 237 additions and 146 deletions

View File

@@ -264,7 +264,6 @@ For security reasons, enabling this is not recommended.''',
('use_video_download', {
'type': int,
'default': 0,
'comment': '',
'options': [
(0, 'Disabled'),
(1, 'Enabled'),
@@ -471,7 +470,7 @@ upgrade_functions = {
def log_ignored_line(line_number, message):
print("WARNING: Ignoring settings.txt line " + str(node.lineno) + " (" + message + ")")
print('WARNING: Ignoring settings.txt line ' + str(line_number) + ' (' + message + ')')
if os.path.isfile("settings.txt"):
@@ -511,17 +510,17 @@ else:
pass # Removed in Python 3.12+
module_node = ast.parse(settings_text)
for node in module_node.body:
if type(node) != ast.Assign:
log_ignored_line(node.lineno, "only assignments are allowed")
if not isinstance(node, ast.Assign):
log_ignored_line(node.lineno, 'only assignments are allowed')
continue
if len(node.targets) > 1:
log_ignored_line(node.lineno, "only simple single-variable assignments allowed")
log_ignored_line(node.lineno, 'only simple single-variable assignments allowed')
continue
target = node.targets[0]
if type(target) != ast.Name:
log_ignored_line(node.lineno, "only simple single-variable assignments allowed")
if not isinstance(target, ast.Name):
log_ignored_line(node.lineno, 'only simple single-variable assignments allowed')
continue
if target.id not in acceptable_targets: