diff --git a/concore_cli/commands/validate.py b/concore_cli/commands/validate.py index fa1ea18..f8265b9 100644 --- a/concore_cli/commands/validate.py +++ b/concore_cli/commands/validate.py @@ -84,6 +84,11 @@ def validate_workflow(workflow_file, console): label = label_tag.text.strip() node_labels.append(label) + # reject shell metacharacters to prevent command injection (#251) + if re.search(r'[;&|`$\'"()\\]', label): + errors.append(f"Node '{label}' contains unsafe shell characters") + continue + if ':' not in label: warnings.append(f"Node '{label}' missing format 'ID:filename'") else: diff --git a/tests/test_graph.py b/tests/test_graph.py index 97102dc..f6e2825 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -112,6 +112,23 @@ def test_validate_node_missing_filename(self): self.assertIn('Validation failed', result.output) self.assertIn('has no filename', result.output) + def test_validate_unsafe_node_label(self): + content = ''' + + + + n0;rm -rf /:script.py + + + + ''' + filepath = self.create_graph_file('injection.graphml', content) + + result = self.runner.invoke(cli, ['validate', filepath]) + + self.assertIn('Validation failed', result.output) + self.assertIn('unsafe shell characters', result.output) + def test_validate_valid_graph(self): content = '''