diff --git a/docs/source/advanced_usage/detailed_operations.rst b/docs/source/advanced_usage/detailed_operations.rst index af9ff7dfcd..66fd3c8cfd 100644 --- a/docs/source/advanced_usage/detailed_operations.rst +++ b/docs/source/advanced_usage/detailed_operations.rst @@ -66,3 +66,11 @@ When installing a ``noarch: python`` package, the installation process will comp All installed files are later referenced in the ``$TARGET_PREFIX/conda-meta/mypkg-version-build.json`` file, to facilitate the removal (e.g. when upgrading or removing a package). If the package contains a ``menu/*.json`` entry that follows the spec introduced by ``menuinst``, a start-menu entry is created on Windows. This is currently not implemented on Linux or macOS but that might change in the future. + +Package link scripts +-------------------- + +Some packages also include ``pre-link``, ``post-link``, ``pre-unlink``, +or ``post-unlink`` scripts that are executed during the linking step. +Because these scripts can contain arbitrary code, they have security +implications. See :ref:`security` for more details. diff --git a/docs/source/advanced_usage/security.rst b/docs/source/advanced_usage/security.rst new file mode 100644 index 0000000000..6e1a8d2286 --- /dev/null +++ b/docs/source/advanced_usage/security.rst @@ -0,0 +1,54 @@ +.. _security: + +Security +======== + +Package link scripts +-------------------- + +Some packages include ``pre-link``, ``post-link``, ``pre-unlink``, or +``post-unlink`` scripts that execute during package installation, update +or removal. + +Note that ``post-unlink`` scripts are **deprecated** and therefore +not executed. + +These scripts run in a shell subprocess and can perform arbitrary +operations on the system. + +``pre-link`` scripts are particularly high-risk: they run from the +package cache and can modify the package itself, affecting all +environments that use that cached package. + +By default, Mamba shows a security warning in case a transaction +involves packages with scripts and prompts for confirmation. + +Disabling link scripts +^^^^^^^^^^^^^^^^^^^^^^ + +You can disable execution of all link scripts using the +``--skip-run-link-scripts`` flag: + +.. code-block:: bash + + mamba install somepackage --skip-run-link-scripts + mamba remove somepackage --skip-run-link-scripts + +Or via configuration: + +.. code-block:: yaml + + # ~/.mambarc + skip_run_link_scripts: true + +.. code-block:: bash + + export MAMBA_SKIP_RUN_LINK_SCRIPTS=true + +When disabled, scripts are silently skipped and the security warning +is suppressed. + +.. note:: + Some packages rely on link scripts for correct setup. + Disable only when you understand the trade-offs, such as when + installing from untrusted sources. diff --git a/docs/source/index.rst b/docs/source/index.rst index 7c658cd3a6..45792f85f0 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -48,8 +48,9 @@ You can try Mamba now by visiting the installation for advanced_usage/more_concepts advanced_usage/detailed_operations - advanced_usage/artifacts_verification advanced_usage/package_resolution + advanced_usage/artifacts_verification + advanced_usage/security .. toctree:: :caption: LIBMAMBA USAGE diff --git a/docs/source/installation/mamba-installation.rst b/docs/source/installation/mamba-installation.rst index 1826fa7b64..3e3fd739df 100644 --- a/docs/source/installation/mamba-installation.rst +++ b/docs/source/installation/mamba-installation.rst @@ -65,7 +65,7 @@ uninstalling ``mamba`` involves removing the entire Miniforge installation. Use these paths to adapt the commands below to your specific installation. 1. Remove shell initialization - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you initialized your shell with ``mamba shell init``, you need to remove the initialization code from your shell configuration files. Run the following command for each shell you initialized: @@ -88,7 +88,7 @@ uninstalling ``mamba`` involves removing the entire Miniforge installation. This will remove the mamba initialization block from your shell configuration files (``.bashrc``, ``.zshrc``, ``config.fish``, etc.). 2. Remove the Miniforge installation directory and package cache - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The Miniforge installation directory contains ``mamba``, ``conda``, and all installed packages and environments. Check ``mamba info`` to find the exact location: @@ -129,7 +129,7 @@ uninstalling ``mamba`` involves removing the entire Miniforge installation. Make sure you have backed up any important environments or data before removing this directory. 3. Remove configuration files (optional) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Check ``mamba info`` for the exact paths to your configuration files: @@ -157,7 +157,7 @@ uninstalling ``mamba`` involves removing the entire Miniforge installation. If you also use ``conda`` from another distribution (like Anaconda), be careful not to delete shared configuration files that are still needed. 4. Remove from PATH (if manually added) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you manually added Miniforge to your PATH, remove those entries from your shell configuration files (``.bashrc``, ``.zshrc``, ``.profile``, etc.). diff --git a/docs/source/installation/micromamba-installation.rst b/docs/source/installation/micromamba-installation.rst index 5b14c9b6a6..f36b602e63 100644 --- a/docs/source/installation/micromamba-installation.rst +++ b/docs/source/installation/micromamba-installation.rst @@ -272,7 +272,7 @@ To completely remove ``micromamba`` from your system, follow these steps: Use these paths to adapt the commands below to your specific installation. 1. Remove shell initialization - ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you initialized your shell with ``micromamba shell init``, you need to remove the initialization code from your shell configuration files. Run the following command for each shell you initialized: @@ -295,7 +295,7 @@ To completely remove ``micromamba`` from your system, follow these steps: This will remove the mamba initialization block from your shell configuration files (``.bashrc``, ``.zshrc``, ``config.fish``, etc.). 2. Remove the micromamba executable - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The location of the ``micromamba`` executable depends on your installation method: @@ -311,7 +311,7 @@ To completely remove ``micromamba`` from your system, follow these steps: Remove the directory where you extracted or installed the ``micromamba`` executable. 3. Remove the root prefix directory and package cache - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``micromamba`` stores all environments, packages, and cache in specific directories. Check ``micromamba info`` to find the exact locations for your installation: @@ -346,7 +346,7 @@ To completely remove ``micromamba`` from your system, follow these steps: This will delete all environments, installed packages, and cached data. Make sure you have backed up any important environments or data before removing this directory. 4. Remove configuration files (optional) - ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Check ``micromamba info`` for the exact paths to your configuration files: diff --git a/libmamba/include/mamba/core/context_params.hpp b/libmamba/include/mamba/core/context_params.hpp index 136ebcfe62..ed2ce8fede 100644 --- a/libmamba/include/mamba/core/context_params.hpp +++ b/libmamba/include/mamba/core/context_params.hpp @@ -38,6 +38,7 @@ namespace mamba bool always_copy = false; bool always_softlink = false; bool compile_pyc = true; + bool skip_run_link_scripts = false; }; struct ThreadsParams diff --git a/libmamba/src/api/configuration.cpp b/libmamba/src/api/configuration.cpp index 59dc881821..bfe588fb6f 100644 --- a/libmamba/src/api/configuration.cpp +++ b/libmamba/src/api/configuration.cpp @@ -1893,6 +1893,14 @@ namespace mamba .set_env_var_names() .description("Defines if PYC files will be compiled or not")); + insert( + Configurable("skip_run_link_scripts", &m_context.link_params.skip_run_link_scripts) + .group("Extract, Link & Install") + .set_rc_configurable() + .set_env_var_names() + .description("Whether pre/post-un/link scripts will be skipped. Defaults to false.") + ); + insert( Configurable("use_uv", &m_context.use_uv) .group("Extract, Link & Install") diff --git a/libmamba/src/core/link.cpp b/libmamba/src/core/link.cpp index e706feea1f..398d512b3a 100644 --- a/libmamba/src/core/link.cpp +++ b/libmamba/src/core/link.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -567,7 +568,14 @@ namespace mamba return true; } - std::map envmap; + if (transaction_params.link_params.skip_run_link_scripts) + { + LOG_DEBUG << "Skipping " << action << " script for '" << pkg_info.name + << "' (disabled via config)"; + return true; + } + + std::unordered_map envmap; if (action == "pre-link") { LOG_WARNING diff --git a/libmamba/src/core/transaction.cpp b/libmamba/src/core/transaction.cpp index 3a5b402f53..f94677f7ce 100644 --- a/libmamba/src/core/transaction.cpp +++ b/libmamba/src/core/transaction.cpp @@ -1172,9 +1172,12 @@ namespace mamba return true; } - LOG_WARNING - << "Security Warning: This transaction includes executing package scripts (pre/post-link/unlink) if present. " - << "These scripts can contain arbitrary code. Please ensure you trust the package sources."; + if (!ctx.link_params.skip_run_link_scripts) + { + LOG_WARNING + << "Security Warning: This transaction includes executing package scripts (pre/post-link/unlink) if present. " + << "These scripts can contain arbitrary code. Please ensure you trust the package sources."; + } return Console::prompt("Confirm changes", 'y'); } diff --git a/libmamba/tests/src/core/test_configuration.cpp b/libmamba/tests/src/core/test_configuration.cpp index d1f63168d5..a202438ce1 100644 --- a/libmamba/tests/src/core/test_configuration.cpp +++ b/libmamba/tests/src/core/test_configuration.cpp @@ -1127,6 +1127,10 @@ namespace mamba TEST_BOOL_CONFIGURABLE(always_copy, ctx.link_params.always_copy); + TEST_BOOL_CONFIGURABLE(compile_pyc, ctx.link_params.compile_pyc); + + TEST_BOOL_CONFIGURABLE(skip_run_link_scripts, ctx.link_params.skip_run_link_scripts); + TEST_CASE_METHOD(Configuration, "always_softlink_and_copy") { util::set_env("MAMBA_ALWAYS_COPY", "true"); diff --git a/libmamba/tests/src/core/test_link_scripts.cpp b/libmamba/tests/src/core/test_link_scripts.cpp index f9cfb5ee7c..9e6d63d9b5 100644 --- a/libmamba/tests/src/core/test_link_scripts.cpp +++ b/libmamba/tests/src/core/test_link_scripts.cpp @@ -57,14 +57,16 @@ namespace mamba fs::create_directories(prefix / "bin"); auto conda_path = prefix / "bin" / "conda"; auto out = open_ofstream(conda_path); - out << "#!/bin/sh\n"; - out << "if [ \"$1\" = \"shell.posix\" ] && [ \"$2\" = \"hook\" ]; then\n"; - out << " echo 'conda() { :; }'\n"; // Define conda as a no-op function - out << "fi\n"; + out << R"(#!/bin/sh + if [ "$1" = "shell.posix" ] && [ "$2" = "hook" ]; then + # Define conda as a no-op function + echo 'conda() { :; }' + fi + )"; make_executable(conda_path); } - const auto make_tx_context = [&] + const auto make_tx_context = [&](const LinkParams& link_params = {}) { TransactionParams tx_params{ .is_mamba_exe = false, @@ -80,7 +82,7 @@ namespace mamba .conda_prefix = prefix, .relocate_prefix = prefix, }, - .link_params = {}, + .link_params = link_params, .threads_params = {}, }; @@ -92,11 +94,11 @@ namespace mamba ); }; - std::string script_ext = util::on_win ? ".bat" : ".sh"; - std::string pre_link_name = ".test_pkg-pre-link" + script_ext; - std::string post_link_name = ".test_pkg-post-link" + script_ext; - std::string pre_unlink_name = ".test_pkg-pre-unlink" + script_ext; - std::string post_unlink_name = ".test_pkg-post-unlink" + script_ext; + const std::string script_ext = util::on_win ? ".bat" : ".sh"; + const std::string pre_link_name = ".test_pkg-pre-link" + script_ext; + const std::string post_link_name = ".test_pkg-post-link" + script_ext; + const std::string pre_unlink_name = ".test_pkg-pre-unlink" + script_ext; + const std::string post_unlink_name = ".test_pkg-post-unlink" + script_ext; auto create_script = [](const fs::u8path& p, const fs::u8path& marker_path) { @@ -165,7 +167,22 @@ namespace mamba REQUIRE(fs::exists(pre_unlink_marker)); // post-unlink script should not be executed as deprecated - REQUIRE(!fs::exists(post_unlink_marker)); + REQUIRE_FALSE(fs::exists(post_unlink_marker)); + } + + SECTION("link scripts disabled") + { + auto tx_context = make_tx_context({ .skip_run_link_scripts = true }); + LinkPackage link_pkg(pkg, cache_dir, &tx_context); + UnlinkPackage unlink_pkg(pkg, cache_dir, &tx_context); + + REQUIRE(link_pkg.execute()); + REQUIRE(unlink_pkg.execute()); + + REQUIRE_FALSE(fs::exists(pre_link_marker)); + REQUIRE_FALSE(fs::exists(post_link_marker)); + REQUIRE_FALSE(fs::exists(pre_unlink_marker)); + REQUIRE_FALSE(fs::exists(post_unlink_marker)); } } } diff --git a/libmambapy/bindings/legacy.cpp b/libmambapy/bindings/legacy.cpp index 886cbe115d..522a532632 100644 --- a/libmambapy/bindings/legacy.cpp +++ b/libmambapy/bindings/legacy.cpp @@ -1137,25 +1137,32 @@ bind_submodule_impl(pybind11::module_ m) pyLinkParams .def( py::init( - [](bool allow_softlinks, bool always_copy, bool always_softlink, bool compile_pyc) -> LinkParams + [](bool allow_softlinks, + bool always_copy, + bool always_softlink, + bool compile_pyc, + bool skip_run_link_scripts) -> LinkParams { return { .allow_softlinks = allow_softlinks, .always_copy = always_copy, .always_softlink = always_softlink, .compile_pyc = compile_pyc, + .skip_run_link_scripts = skip_run_link_scripts, }; } ), py::arg("allow_softlinks") = default_link_params.allow_softlinks, py::arg("always_copy") = default_link_params.always_copy, py::arg("always_softlink") = default_link_params.always_softlink, - py::arg("compile_pyc") = default_link_params.compile_pyc + py::arg("compile_pyc") = default_link_params.compile_pyc, + py::arg("skip_run_link_scripts") = default_link_params.skip_run_link_scripts ) .def_readwrite("allow_softlinks", &LinkParams::allow_softlinks) .def_readwrite("always_copy", &LinkParams::always_copy) .def_readwrite("always_softlink", &LinkParams::always_softlink) - .def_readwrite("compile_pyc", &LinkParams::compile_pyc); + .def_readwrite("compile_pyc", &LinkParams::compile_pyc) + .def_readwrite("skip_run_link_scripts", &LinkParams::skip_run_link_scripts); static const auto default_validation_params = ValidationParams{}; pyValidationParams diff --git a/micromamba/src/common_options.cpp b/micromamba/src/common_options.cpp index b1e3ff0e61..9c17e767b0 100644 --- a/micromamba/src/common_options.cpp +++ b/micromamba/src/common_options.cpp @@ -346,6 +346,17 @@ no_channel_priority_hook(Configuration& config, bool&) } } +void +init_link_options(CLI::App* subcom, Configuration& config) +{ + auto& skip_run_link_scripts = config.at("skip_run_link_scripts"); + subcom->add_flag( + "--skip-run-link-scripts,!--run-link-scripts", + skip_run_link_scripts.get_cli_config(), + skip_run_link_scripts.description() + ); +} + void init_install_options(CLI::App* subcom, Configuration& config) { @@ -354,6 +365,7 @@ init_install_options(CLI::App* subcom, Configuration& config) init_prefix_options(subcom, config); init_network_options(subcom, config); init_channel_parser(subcom, config); + init_link_options(subcom, config); auto& specs = config.at("specs"); subcom diff --git a/micromamba/src/common_options.hpp b/micromamba/src/common_options.hpp index 1449d2da6d..c678fee296 100644 --- a/micromamba/src/common_options.hpp +++ b/micromamba/src/common_options.hpp @@ -22,6 +22,9 @@ init_general_options(CLI::App* subcom, mamba::Configuration& config); void init_prefix_options(CLI::App* subcom, mamba::Configuration& config); +void +init_link_options(CLI::App* subcom, mamba::Configuration& config); + void init_install_options(CLI::App* subcom, mamba::Configuration& config); diff --git a/micromamba/src/env.cpp b/micromamba/src/env.cpp index 85014534b7..0e211c9ac4 100644 --- a/micromamba/src/env.cpp +++ b/micromamba/src/env.cpp @@ -295,6 +295,7 @@ set_env_command(CLI::App* com, mamba::Configuration& config) auto* remove_subcom = com->add_subcommand("remove", "Remove an environment"); init_general_options(remove_subcom, config); init_prefix_options(remove_subcom, config); + init_link_options(remove_subcom, config); remove_subcom->callback( [&config] @@ -351,6 +352,7 @@ set_env_command(CLI::App* com, mamba::Configuration& config) init_general_options(update_subcom, config); init_prefix_options(update_subcom, config); + init_link_options(update_subcom, config); auto& file_specs = config.at("file_specs"); update_subcom diff --git a/micromamba/src/remove.cpp b/micromamba/src/remove.cpp index d07f7a6cb0..814872e14b 100644 --- a/micromamba/src/remove.cpp +++ b/micromamba/src/remove.cpp @@ -18,6 +18,7 @@ set_remove_command(CLI::App* subcom, Configuration& config) using string_list = std::vector; init_general_options(subcom, config); init_prefix_options(subcom, config); + init_link_options(subcom, config); auto& specs = config.at("specs"); subcom