n8n/packages/@n8n/task-runner-python/src/import_validation.py
Emilia 46aa8e4af7
fix(core): Let allowlisted Python packages import their own submodules via relative imports (#32772)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 08:32:02 +00:00

48 lines
1.6 KiB
Python

import importlib.util
import sys
from src.config.security_config import SecurityConfig
from src.constants import ERROR_STDLIB_DISALLOWED, ERROR_EXTERNAL_DISALLOWED
def validate_module_import(
module_path: str,
security_config: SecurityConfig,
importing_package: str | None = None,
) -> tuple[bool, str | None]:
stdlib_allow = security_config.stdlib_allow
external_allow = security_config.external_allow
# Resolve a package-relative name. This always resolves within its own
# top-level package, so this never reaches a package outside the allowlist.
if module_path.startswith(".") and importing_package:
try:
module_path = importlib.util.resolve_name(module_path, importing_package)
except (ImportError, ValueError):
pass
module_name = module_path.split(".")[0]
is_stdlib = module_name in sys.stdlib_module_names
is_external = not is_stdlib
if is_stdlib and ("*" in stdlib_allow or module_name in stdlib_allow):
return (True, None)
if is_external and ("*" in external_allow or module_name in external_allow):
return (True, None)
if is_stdlib:
stdlib_allowed_str = ", ".join(sorted(stdlib_allow)) if stdlib_allow else "none"
error_msg = ERROR_STDLIB_DISALLOWED.format(
module=module_path, allowed=stdlib_allowed_str
)
else:
external_allowed_str = (
", ".join(sorted(external_allow)) if external_allow else "none"
)
error_msg = ERROR_EXTERNAL_DISALLOWED.format(
module=module_path, allowed=external_allowed_str
)
return (False, error_msg)