diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 609d0e39a9..51837537dc 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -15,6 +15,7 @@ --> # Unreleased version ## Backward incompatibility +* The deprecated Snowpark annotation processor (`processors: [snowpark]` in a Native App project definition) is now opt-in. Bundling or deploying a Native App with a `snowpark` processor no longer executes it unless the `ENABLE_SNOWPARK_ANNOTATION_PROCESSOR` feature flag is turned on (`snowflake.cli.features.enable_snowpark_annotation_processor = true` in `config.toml`, or `SNOWFLAKE_CLI_FEATURES_ENABLE_SNOWPARK_ANNOTATION_PROCESSOR=1`). The processor's job is to execute every annotated `.py` artifact in a subprocess so that module-level objects can be introspected; gating it behind an explicit opt-in aligns its long-deprecated status with its cost and closes an arbitrary-code-execution path for repository-level attack scenarios. ## Deprecations diff --git a/src/snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py b/src/snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py index f8caf18b90..a686023c07 100644 --- a/src/snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py +++ b/src/snowflake/cli/_plugins/nativeapp/codegen/snowpark/python_processor.py @@ -51,6 +51,7 @@ from snowflake.cli.api.cli_global_context import get_cli_context, span from snowflake.cli.api.console import cli_console from snowflake.cli.api.console import cli_console as cc +from snowflake.cli.api.feature_flags import FeatureFlag from snowflake.cli.api.metrics import CLICounterField from snowflake.cli.api.project.schemas.entities.common import ( PathMapping, @@ -170,6 +171,10 @@ class SnowparkAnnotationProcessor(ArtifactProcessor): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + @staticmethod + def is_enabled() -> bool: + return FeatureFlag.ENABLE_SNOWPARK_ANNOTATION_PROCESSOR.is_enabled() + @span("snowpark_processor") def process( self, diff --git a/src/snowflake/cli/api/feature_flags.py b/src/snowflake/cli/api/feature_flags.py index 35c0401bb2..834e797015 100644 --- a/src/snowflake/cli/api/feature_flags.py +++ b/src/snowflake/cli/api/feature_flags.py @@ -64,6 +64,9 @@ class FeatureFlag(FeatureFlagMixin): ENABLE_NATIVE_APP_PYTHON_SETUP = BooleanFlag( "ENABLE_NATIVE_APP_PYTHON_SETUP", False ) + ENABLE_SNOWPARK_ANNOTATION_PROCESSOR = BooleanFlag( + "ENABLE_SNOWPARK_ANNOTATION_PROCESSOR", False + ) ENABLE_NATIVE_APP_CHILDREN = BooleanFlag("ENABLE_NATIVE_APP_CHILDREN", False) # TODO 4.0: remove ENABLE_RELEASE_CHANNELS ENABLE_RELEASE_CHANNELS = BooleanFlag("ENABLE_RELEASE_CHANNELS", None) diff --git a/tests/nativeapp/test_feature_flags.py b/tests/nativeapp/test_feature_flags.py index 412f70b5d5..fe231da3c9 100644 --- a/tests/nativeapp/test_feature_flags.py +++ b/tests/nativeapp/test_feature_flags.py @@ -29,3 +29,19 @@ def test_feature_setup_script_generation_enabled( mock_get_config_value.assert_called_once_with( "cli", "features", key="enable_native_app_python_setup", default=None ) + + +@mock.patch("snowflake.cli.api.config.get_config_value") +@pytest.mark.parametrize("value_from_config", [True, False]) +def test_feature_snowpark_annotation_processor_enabled( + mock_get_config_value, value_from_config +): + mock_get_config_value.return_value = value_from_config + + assert ( + FeatureFlag.ENABLE_SNOWPARK_ANNOTATION_PROCESSOR.is_enabled() + is value_from_config + ) + mock_get_config_value.assert_called_once_with( + "cli", "features", key="enable_snowpark_annotation_processor", default=None + )