|
6 | 6 | """ |
7 | 7 |
|
8 | 8 | import os |
| 9 | +import re |
| 10 | +import shlex |
9 | 11 | import subprocess |
10 | 12 | from pathlib import Path |
11 | 13 |
|
12 | 14 |
|
| 15 | +def _validate_job_id(job_id: str) -> bool: |
| 16 | + """Validate job_id is numeric only to prevent command injection.""" |
| 17 | + if not re.fullmatch(r"\d+", job_id): |
| 18 | + print(f" Error: Invalid job_id '{job_id}'. Must be numeric.") |
| 19 | + return False |
| 20 | + return True |
| 21 | + |
| 22 | + |
13 | 23 | def parse_jumpbox_uri(jumpbox_uri: str) -> tuple[str, str | None]: |
14 | 24 | """ |
15 | 25 | Parse JUMPBOX_URI format: "user@host -p port" or "user@host". |
@@ -80,9 +90,7 @@ def download_from_jumpbox(job_id: str, jumpbox_uri: str | None = None) -> bool: |
80 | 90 | Returns: |
81 | 91 | True on success, False on failure |
82 | 92 | """ |
83 | | - # Sanitize job_id to prevent path traversal |
84 | | - if "/" in job_id or "." in job_id: |
85 | | - print(" Error: Invalid job_id format. Cannot contain . or /") |
| 93 | + if not _validate_job_id(job_id): |
86 | 94 | return False |
87 | 95 |
|
88 | 96 | # Get JUMPBOX_URI |
@@ -124,7 +132,7 @@ def download_from_jumpbox(job_id: str, jumpbox_uri: str | None = None) -> bool: |
124 | 132 | ssh_cmd = ["ssh"] |
125 | 133 | if ssh_port: |
126 | 134 | ssh_cmd.extend(["-p", ssh_port]) |
127 | | - ssh_cmd.extend([ssh_target, f"test -d {candidate}"]) |
| 135 | + ssh_cmd.extend([ssh_target, f"test -d {shlex.quote(candidate)}"]) |
128 | 136 |
|
129 | 137 | try: |
130 | 138 | subprocess.run( |
@@ -194,9 +202,7 @@ def upload_to_jumpbox(job_id: str, jumpbox_uri: str | None = None) -> bool: |
194 | 202 | Returns: |
195 | 203 | True on success, False on failure |
196 | 204 | """ |
197 | | - # Sanitize job_id to prevent path traversal |
198 | | - if "/" in job_id or "." in job_id: |
199 | | - print(" Error: Invalid job_id format. Cannot contain . or /") |
| 205 | + if not _validate_job_id(job_id): |
200 | 206 | return False |
201 | 207 |
|
202 | 208 | local_file = Path(".analysis") / job_id / "annotation_draft.json" |
|
0 commit comments