From 81b523affb703ee6d074e62600168b3a652d7380 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 24 Feb 2026 03:52:38 +0000 Subject: [PATCH] Fix path traversal and argument injection in launcher.py Co-authored-by: alfieprojectsdev <11991855+alfieprojectsdev@users.noreply.github.com> --- field-logic/launcher.py | 20 +++++- field-logic/test_launcher_security.py | 89 +++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 field-logic/test_launcher_security.py diff --git a/field-logic/launcher.py b/field-logic/launcher.py index c79ca6a..8421c7c 100644 --- a/field-logic/launcher.py +++ b/field-logic/launcher.py @@ -21,11 +21,29 @@ def main(): filename = query.get('file', [''])[0] timestamp = query.get('t', ['0'])[0] + # Validate timestamp + try: + float(timestamp) + except ValueError: + print("Error: Invalid timestamp") + return + if not filename: print("Error: No filename provided") return - file_path = os.path.join(MEDIA_DIR, filename) + # Security check: Prevent path traversal + base_dir = os.path.abspath(MEDIA_DIR) + file_path = os.path.abspath(os.path.join(base_dir, filename)) + + try: + if os.path.commonpath([base_dir, file_path]) != base_dir: + print("Error: Invalid filename (path traversal detected)") + return + except ValueError: + # Handles cases like different drives on Windows + print("Error: Invalid filename (path traversal detected)") + return # 2. Launch VLC # vlc "path" --start-time=X diff --git a/field-logic/test_launcher_security.py b/field-logic/test_launcher_security.py new file mode 100644 index 0000000..3da0323 --- /dev/null +++ b/field-logic/test_launcher_security.py @@ -0,0 +1,89 @@ +import unittest +from unittest.mock import patch, MagicMock +import os +import sys +import io + +# Add current directory to path so we can import launcher +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +import launcher + +class TestLauncherSecurity(unittest.TestCase): + @patch('subprocess.Popen') + def test_path_traversal_prevention(self, mock_popen): + # Mock configuration + launcher.MEDIA_DIR = '/tmp/media' + launcher.VLC_PATH = '/usr/bin/vlc' + + filename = '../secret.txt' + uri = f'fieldlogic://open?file={filename}&t=0' + + with patch.object(sys, 'argv', ['launcher.py', uri]): + with patch('sys.stdout', new=io.StringIO()) as fake_stdout: + launcher.main() + output = fake_stdout.getvalue() + + self.assertFalse(mock_popen.called, "subprocess.Popen should not be called for path traversal attempt") + self.assertIn("Error: Invalid filename (path traversal detected)", output) + + @patch('subprocess.Popen') + def test_absolute_path_prevention(self, mock_popen): + # Mock configuration + launcher.MEDIA_DIR = '/tmp/media' + launcher.VLC_PATH = '/usr/bin/vlc' + + filename = '/etc/passwd' + uri = f'fieldlogic://open?file={filename}&t=0' + + with patch.object(sys, 'argv', ['launcher.py', uri]): + with patch('sys.stdout', new=io.StringIO()) as fake_stdout: + launcher.main() + output = fake_stdout.getvalue() + + self.assertFalse(mock_popen.called, "subprocess.Popen should not be called for absolute path attempt") + self.assertIn("Error: Invalid filename (path traversal detected)", output) + + @patch('subprocess.Popen') + def test_invalid_timestamp(self, mock_popen): + # Mock configuration + launcher.MEDIA_DIR = '/tmp/media' + launcher.VLC_PATH = '/usr/bin/vlc' + + filename = 'interview.webm' + uri = f'fieldlogic://open?file={filename}&t=not-a-number' + + with patch.object(sys, 'argv', ['launcher.py', uri]): + with patch('sys.stdout', new=io.StringIO()) as fake_stdout: + launcher.main() + output = fake_stdout.getvalue() + + self.assertFalse(mock_popen.called, "subprocess.Popen should not be called for invalid timestamp") + self.assertIn("Error: Invalid timestamp", output) + + @patch('subprocess.Popen') + def test_valid_path(self, mock_popen): + # Mock configuration + launcher.MEDIA_DIR = '/tmp/media' + launcher.VLC_PATH = '/usr/bin/vlc' + + filename = 'interview_01.webm' + uri = f'fieldlogic://open?file={filename}&t=123.45' + + with patch.object(sys, 'argv', ['launcher.py', uri]): + with patch('sys.stdout', new=io.StringIO()) as fake_stdout: + launcher.main() + output = fake_stdout.getvalue() + + self.assertTrue(mock_popen.called, "subprocess.Popen should be called for valid path") + args, _ = mock_popen.call_args + cmd = args[0] + file_path = cmd[1] + timestamp_arg = cmd[2] + + expected_path = os.path.abspath(os.path.join('/tmp/media', filename)) + self.assertEqual(file_path, expected_path) + self.assertEqual(timestamp_arg, "--start-time=123.45") + +if __name__ == '__main__': + unittest.main()