diff --git a/.github/workflows/ci-python.yml b/.github/workflows/ci-python.yml index b7bad9321ba..f7201d706e8 100644 --- a/.github/workflows/ci-python.yml +++ b/.github/workflows/ci-python.yml @@ -35,7 +35,7 @@ jobs: run: just sync-all - name: Format check - run: just format check + run: just format-check - name: Typecheck run: just typecheck diff --git a/packages/@n8n/task-runner-python/src/health_check_server.py b/packages/@n8n/task-runner-python/src/health_check_server.py index f1bbe6f7270..2f191ed3ebf 100644 --- a/packages/@n8n/task-runner-python/src/health_check_server.py +++ b/packages/@n8n/task-runner-python/src/health_check_server.py @@ -20,7 +20,8 @@ class HealthCheckServer: self.server = await asyncio.start_server( self._handle_request, config.host, config.port ) - actual_port = self.server.sockets[0].getsockname()[1] # for OS-assigned port in tests + # for OS-assigned port in tests + actual_port = self.server.sockets[0].getsockname()[1] self.logger.info( f"Health check server listening on {config.host}, port {actual_port}" ) diff --git a/packages/@n8n/task-runner-python/src/task_analyzer.py b/packages/@n8n/task-runner-python/src/task_analyzer.py index f335dbe0501..f40a084d11c 100644 --- a/packages/@n8n/task-runner-python/src/task_analyzer.py +++ b/packages/@n8n/task-runner-python/src/task_analyzer.py @@ -87,22 +87,32 @@ class SecurityValidator(ast.NodeVisitor): self.generic_visit(node) def visit_Subscript(self, node: ast.Subscript) -> None: - """Detect dict access to blocked attributes, e.g. __builtins__['__spec__']""" - + """Detect dict access to blocked attributes, e.g. __builtins__['__spec__']""" + is_builtins_access = ( # __builtins__['__spec__'] - (isinstance(node.value, ast.Name) and node.value.id in {"__builtins__", "builtins"}) + ( + isinstance(node.value, ast.Name) + and node.value.id in {"__builtins__", "builtins"} + ) # obj.__builtins__['__spec__'] - or (isinstance(node.value, ast.Attribute) and node.value.attr in {"__builtins__", "builtins"}) + or ( + isinstance(node.value, ast.Attribute) + and node.value.attr in {"__builtins__", "builtins"} + ) ) - - if is_builtins_access and isinstance(node.slice, ast.Constant) and isinstance(node.slice.value, str): + + if ( + is_builtins_access + and isinstance(node.slice, ast.Constant) + and isinstance(node.slice.value, str) + ): key = node.slice.value if key in BLOCKED_ATTRIBUTES: self._add_violation( node.lineno, ERROR_DANGEROUS_ATTRIBUTE.format(attr=key) ) - + self.generic_visit(node) # ========== Validation ========== diff --git a/packages/@n8n/task-runner-python/tests/integration/test_execution.py b/packages/@n8n/task-runner-python/tests/integration/test_execution.py index f88c7a0e547..c9393c8f8bc 100644 --- a/packages/@n8n/task-runner-python/tests/integration/test_execution.py +++ b/packages/@n8n/task-runner-python/tests/integration/test_execution.py @@ -114,7 +114,7 @@ async def test_per_item_with_explicit_json_and_binary(broker, manager): { "json": {"custom": "data"}, "binary": {"file": "data"}, - "pairedItem": {"item": 0} + "pairedItem": {"item": 0}, } ] @@ -130,11 +130,7 @@ async def test_per_item_with_binary_only(broker, manager): result = await wait_for_task_done(broker, task_id) assert result["data"]["result"] == [ - { - "json": {}, - "binary": {"file": "data"}, - "pairedItem": {"item": 0} - } + {"json": {}, "binary": {"file": "data"}, "pairedItem": {"item": 0}} ] @@ -305,7 +301,9 @@ async def test_cannot_bypass_import_restrictions_via_builtins_dict(broker, manag @pytest.mark.asyncio -async def test_cannot_bypass_import_restrictions_via_builtins_spec_loader(broker, manager): +async def test_cannot_bypass_import_restrictions_via_builtins_spec_loader( + broker, manager +): task_id = nanoid() code = textwrap.dedent(""" sys = __builtins__['__spec__'].loader.load_module('sys') @@ -320,8 +318,11 @@ async def test_cannot_bypass_import_restrictions_via_builtins_spec_loader(broker assert error_msg["taskId"] == task_id assert "error" in error_msg + @pytest.mark.asyncio -async def test_cannot_bypass_import_restrictions_via_sys_builtins_spec_leader(broker, manager_with_stdlib_wildcard): +async def test_cannot_bypass_import_restrictions_via_sys_builtins_spec_leader( + broker, manager_with_stdlib_wildcard +): task_id = nanoid() code = textwrap.dedent(""" import sys @@ -331,6 +332,6 @@ async def test_cannot_bypass_import_restrictions_via_sys_builtins_spec_leader(br task_settings = create_task_settings(code=code, node_mode="all_items") await broker.send_task(task_id=task_id, task_settings=task_settings) error_msg = await wait_for_task_error(broker, task_id) - + assert error_msg["taskId"] == task_id assert "error" in error_msg