@@ -18,7 +18,7 @@ def test_workflow_id_sql_injection_single_quote(test_client):
1818 "/api/workflows/test' OR '1'='1/enable" ,
1919 headers = test_client .auth_headers ,
2020 )
21- assert resp .status_code in ( 400 , 404 , 422 )
21+ assert resp .status_code == 400
2222
2323
2424def test_workflow_id_sql_injection_union (test_client ):
@@ -27,7 +27,7 @@ def test_workflow_id_sql_injection_union(test_client):
2727 "/api/workflows/test' UNION SELECT * FROM users--/enable" ,
2828 headers = test_client .auth_headers ,
2929 )
30- assert resp .status_code in ( 400 , 404 , 422 )
30+ assert resp .status_code == 400
3131
3232
3333def test_workflow_id_command_injection_semicolon (test_client ):
@@ -36,7 +36,7 @@ def test_workflow_id_command_injection_semicolon(test_client):
3636 "/api/workflows/test;rm -rf //enable" ,
3737 headers = test_client .auth_headers ,
3838 )
39- assert resp .status_code in ( 400 , 404 , 422 )
39+ assert resp .status_code == 400
4040
4141
4242def test_workflow_id_command_injection_pipe (test_client ):
@@ -45,7 +45,7 @@ def test_workflow_id_command_injection_pipe(test_client):
4545 "/api/workflows/test|cat /etc/passwd/enable" ,
4646 headers = test_client .auth_headers ,
4747 )
48- assert resp .status_code in ( 400 , 404 , 422 )
48+ assert resp .status_code == 400
4949
5050
5151def test_workflow_id_command_injection_backtick (test_client ):
@@ -54,7 +54,7 @@ def test_workflow_id_command_injection_backtick(test_client):
5454 "/api/workflows/test`whoami`/enable" ,
5555 headers = test_client .auth_headers ,
5656 )
57- assert resp .status_code in ( 400 , 404 , 422 )
57+ assert resp .status_code == 400
5858
5959
6060def test_workflow_id_null_byte_injection (test_client ):
@@ -63,7 +63,7 @@ def test_workflow_id_null_byte_injection(test_client):
6363 "/api/workflows/test\x00 malicious/enable" ,
6464 headers = test_client .auth_headers ,
6565 )
66- assert resp .status_code in ( 400 , 404 , 422 )
66+ assert resp .status_code == 400
6767
6868
6969def test_workflow_id_url_encoded_traversal (test_client ):
@@ -72,7 +72,7 @@ def test_workflow_id_url_encoded_traversal(test_client):
7272 "/api/workflows/..%2F..%2Fetc%2Fpasswd/enable" ,
7373 headers = test_client .auth_headers ,
7474 )
75- assert resp .status_code in ( 400 , 404 , 422 )
75+ assert resp .status_code == 400
7676
7777
7878def test_workflow_id_double_encoded_traversal (test_client ):
@@ -81,7 +81,7 @@ def test_workflow_id_double_encoded_traversal(test_client):
8181 "/api/workflows/..%252F..%252Fetc%252Fpasswd/enable" ,
8282 headers = test_client .auth_headers ,
8383 )
84- assert resp .status_code in ( 400 , 404 , 422 )
84+ assert resp .status_code == 400
8585
8686
8787# ---------------------------------------------------------------------------
@@ -95,7 +95,7 @@ def test_workflow_id_absolute_path(test_client):
9595 "/api/workflows//etc/passwd/enable" ,
9696 headers = test_client .auth_headers ,
9797 )
98- assert resp .status_code in ( 400 , 404 , 422 )
98+ assert resp .status_code == 400
9999
100100
101101def test_workflow_id_windows_path (test_client ):
@@ -104,7 +104,7 @@ def test_workflow_id_windows_path(test_client):
104104 "/api/workflows/..\\ ..\\ windows\\ system32/enable" ,
105105 headers = test_client .auth_headers ,
106106 )
107- assert resp .status_code in ( 400 , 404 , 422 )
107+ assert resp .status_code == 400
108108
109109
110110def test_workflow_id_mixed_separators (test_client ):
@@ -113,7 +113,7 @@ def test_workflow_id_mixed_separators(test_client):
113113 "/api/workflows/../..\\ /etc/passwd/enable" ,
114114 headers = test_client .auth_headers ,
115115 )
116- assert resp .status_code in ( 400 , 404 , 422 )
116+ assert resp .status_code == 400
117117
118118
119119def test_workflow_id_unicode_traversal (test_client ):
@@ -122,7 +122,7 @@ def test_workflow_id_unicode_traversal(test_client):
122122 "/api/workflows/\u2024 \u2024 /\u2024 \u2024 /etc/passwd/enable" ,
123123 headers = test_client .auth_headers ,
124124 )
125- assert resp .status_code in ( 400 , 404 , 422 )
125+ assert resp .status_code == 400
126126
127127
128128# ---------------------------------------------------------------------------
@@ -212,24 +212,30 @@ def test_preflight_ports_string_injection(test_client):
212212
213213def test_backup_name_command_injection (test_client ):
214214 """Backup action with command injection in name → safe (validated by script path)."""
215- # The backup name is passed as an argument to subprocess.run with list args,
215+ # The backup name is passed as an argument to asyncio.create_subprocess_exec with list args,
216216 # so command injection should not be possible. This test verifies the pattern.
217- with patch ("subprocess.run" ) as mock_run :
218- mock_run .return_value = MagicMock (returncode = 0 , stdout = "" , stderr = "" )
219-
220- # Even with malicious input, subprocess.run with list args is safe
217+ with patch ("asyncio.create_subprocess_exec" ) as mock_exec :
218+ mock_process = AsyncMock ()
219+ mock_process .returncode = 0
220+ mock_process .stdout .read .return_value = b""
221+ mock_process .stderr .read .return_value = b""
222+ mock_exec .return_value = mock_process
223+
224+ # Even with malicious input, asyncio.create_subprocess_exec with list args is safe
221225 resp = test_client .post (
222226 "/api/update" ,
223227 json = {"action" : "backup" },
224228 headers = test_client .auth_headers ,
225229 )
226230
227231 # Should succeed (or fail for other reasons, but not execute injection)
228- # The key is that subprocess.run was called with list args, not shell=True
229- if mock_run .called :
230- call_args = mock_run .call_args
231- # Verify shell=True was NOT used
232- assert call_args [1 ].get ("shell" ) is not True
232+ # The key is that asyncio.create_subprocess_exec was called with list args, not shell=True
233+ if mock_exec .called :
234+ call_args = mock_exec .call_args
235+ # Verify the first argument is the script path, not a shell command
236+ assert len (call_args [0 ]) >= 1 # At least script path
237+ # Verify no shell=True was used (asyncio.create_subprocess_exec doesn't support shell=True anyway)
238+ assert "shell" not in call_args [1 ]
233239
234240
235241def test_update_action_invalid_action (test_client ):
@@ -250,5 +256,5 @@ def test_update_script_path_validation(test_client):
250256 json = {"action" : "check" },
251257 headers = test_client .auth_headers ,
252258 )
253- # Should fail with 501 if script doesn't exist (not 500 or command injection )
254- assert resp .status_code in ( 501 , 500 ) # 501 = not installed, 500 = other error
259+ # Should fail with 501 if script doesn't exist (not 500 crash )
260+ assert resp .status_code == 501
0 commit comments