VYPR
Critical severity9.8NVD Advisory· Published Mar 20, 2025· Updated Apr 15, 2026

CVE-2024-9701

CVE-2024-9701

Description

A Remote Code Execution (RCE) vulnerability has been identified in the Kedro ShelveStore class (version 0.19.8). This vulnerability allows an attacker to execute arbitrary Python code via deserialization of malicious payloads, potentially leading to a full system compromise. The ShelveStore class uses Python's shelve module to manage session data, which relies on pickle for serialization. Crafting a malicious payload and storing it in the shelve file can lead to RCE when the payload is deserialized.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
kedroPyPI
< 0.19.90.19.9

Patches

2
66e5e074b278

Remove `ShelveStore` (#4148)

https://github.com/kedro-org/kedroAnkita KatiyarSep 9, 2024via ghsa
6 files changed · +4 136
  • docs/source/api/kedro.framework.session.shelvestore.ShelveStore.rst+0 6 removed
    @@ -1,6 +0,0 @@
    -kedro.framework.session.shelvestore.ShelveStore
    -================================================
    -
    -.. currentmodule:: kedro.framework.session.shelvestore
    -
    -.. autoclass:: ShelveStore
    
  • kedro/framework/session/shelvestore.py+0 46 removed
    @@ -1,46 +0,0 @@
    -"""This module implements a dict-like store object used to persist Kedro sessions.
    -This module is separated from store.py to ensure it's only imported when exported explicitly.
    -"""
    -
    -from __future__ import annotations
    -
    -import dbm
    -import shelve
    -from multiprocessing import Lock
    -from pathlib import Path
    -from typing import Any
    -
    -from .store import BaseSessionStore
    -
    -
    -class ShelveStore(BaseSessionStore):
    -    """Stores the session data on disk using `shelve` package.
    -    This is an example of how to persist data on disk."""
    -
    -    _lock = Lock()
    -
    -    @property
    -    def _location(self) -> Path:
    -        return Path(self._path).expanduser().resolve() / self._session_id / "store"
    -
    -    def read(self) -> dict[str, Any]:
    -        """Read the data from disk using `shelve` package."""
    -        data: dict[str, Any] = {}
    -        try:
    -            with shelve.open(str(self._location), flag="r") as _sh:  # noqa: S301
    -                data = dict(_sh)
    -        except dbm.error:
    -            pass
    -        return data
    -
    -    def save(self) -> None:
    -        """Save the data on disk using `shelve` package."""
    -        location = self._location
    -        location.parent.mkdir(parents=True, exist_ok=True)
    -
    -        with self._lock, shelve.open(str(location)) as _sh:  # noqa: S301
    -            keys_to_del = _sh.keys() - self.data.keys()
    -            for key in keys_to_del:
    -                del _sh[key]
    -
    -            _sh.update(self.data)
    
  • RELEASE.md+1 0 modified
    @@ -7,6 +7,7 @@
     * Fixed bug where using dataset factories breaks with `ThreadRunner`.
     
     ## Breaking changes to the API
    +* Removed `ShelveStore` to address a security vulnerability.
     
     ## Documentation changes
     * Fix logo on PyPI page.
    
  • tests/framework/project/test_settings.py+3 4 modified
    @@ -7,7 +7,6 @@
     from kedro.config import OmegaConfigLoader
     from kedro.framework.context.context import KedroContext
     from kedro.framework.project import configure_project, settings, validate_settings
    -from kedro.framework.session.shelvestore import ShelveStore
     from kedro.framework.session.store import BaseSessionStore
     from kedro.io import DataCatalog
     
    @@ -40,8 +39,8 @@ def mock_package_name_with_settings_file(tmpdir):
     
                     DISABLE_HOOKS_FOR_PLUGINS = ("kedro-viz",)
     
    -                from kedro.framework.session.shelvestore import ShelveStore
    -                SESSION_STORE_CLASS = ShelveStore
    +                from kedro.framework.session.store import BaseSessionStore
    +                SESSION_STORE_CLASS = BaseSessionStore
                     SESSION_STORE_ARGS = {{
                         "path": "./sessions"
                     }}
    @@ -103,7 +102,7 @@ def test_settings_after_configuring_project_shows_updated_values(
         configure_project(mock_package_name_with_settings_file)
         assert len(settings.HOOKS) == 1 and isinstance(settings.HOOKS[0], ProjectHooks)
         assert settings.DISABLE_HOOKS_FOR_PLUGINS.to_list() == ["kedro-viz"]
    -    assert settings.SESSION_STORE_CLASS is ShelveStore
    +    assert settings.SESSION_STORE_CLASS is BaseSessionStore
         assert settings.SESSION_STORE_ARGS == {"path": "./sessions"}
         assert settings.CONTEXT_CLASS is MyContext
         assert settings.CONF_SOURCE == "test_conf"
    
  • tests/framework/session/test_session.py+0 37 modified
    @@ -22,12 +22,10 @@
         ValidationError,
         Validator,
         _HasSharedParentClassValidator,
    -    _IsSubclassValidator,
         _ProjectSettings,
     )
     from kedro.framework.session import KedroSession
     from kedro.framework.session.session import KedroSessionError
    -from kedro.framework.session.shelvestore import ShelveStore
     from kedro.framework.session.store import BaseSessionStore
     from kedro.utils import _has_rich_handler
     
    @@ -235,21 +233,6 @@ class MockSettings(_ProjectSettings):
         )
     
     
    -@pytest.fixture
    -def mock_settings_shelve_session_store(mocker, fake_project):
    -    shelve_location = fake_project / "nested" / "sessions"
    -
    -    class MockSettings(_ProjectSettings):
    -        _SESSION_STORE_CLASS = _IsSubclassValidator(
    -            "SESSION_STORE_CLASS", default=lambda *_: ShelveStore
    -        )
    -        _SESSION_STORE_ARGS = Validator(
    -            "SESSION_STORE_ARGS", default={"path": shelve_location.as_posix()}
    -        )
    -
    -    return _mock_imported_settings_paths(mocker, MockSettings())
    -
    -
     @pytest.fixture
     def fake_session_id(mocker):
         session_id = "fake_session_id"
    @@ -502,26 +485,6 @@ def test_default_store(self, fake_project, fake_session_id, caplog):
             ]
             assert actual_log_messages == expected_log_messages
     
    -    @pytest.mark.usefixtures("mock_settings_shelve_session_store")
    -    def test_shelve_store(self, fake_project, fake_session_id, caplog, mocker):
    -        mocker.patch("pathlib.Path.is_file", return_value=True)
    -        shelve_location = fake_project / "nested" / "sessions"
    -        other = KedroSession.create(fake_project)
    -        assert other._store.__class__ is ShelveStore
    -        assert other._store._path == shelve_location.as_posix()
    -        assert other._store._location == shelve_location / fake_session_id / "store"
    -        assert other._store._session_id == fake_session_id
    -        assert not shelve_location.is_dir()
    -
    -        other.close()  # session data persisted
    -        assert shelve_location.is_dir()
    -        actual_log_messages = [
    -            rec.getMessage()
    -            for rec in caplog.records
    -            if rec.name == STORE_LOGGER_NAME and rec.levelno == logging.DEBUG
    -        ]
    -        assert not actual_log_messages
    -
         def test_wrong_store_type(self, mock_settings_file_bad_session_store_class):
             pattern = (
                 "Invalid value 'tests.framework.session.test_session.BadStore' received "
    
  • tests/framework/session/test_store.py+0 43 modified
    @@ -1,9 +1,5 @@
     import logging
    -from pathlib import Path
     
    -import pytest
    -
    -from kedro.framework.session.shelvestore import ShelveStore
     from kedro.framework.session.store import BaseSessionStore
     
     FAKE_SESSION_ID = "fake_session_id"
    @@ -48,42 +44,3 @@ def test_save(self, caplog):
                 if rec.name == STORE_LOGGER_NAME and rec.levelno == logging.DEBUG
             ]
             assert actual_debug_messages == expected_debug_messages
    -
    -
    -@pytest.fixture
    -def shelve_path(tmp_path):
    -    return Path(tmp_path / "path" / "to" / "sessions")
    -
    -
    -class TestShelveStore:
    -    def test_empty(self, shelve_path):
    -        shelve = ShelveStore(str(shelve_path), FAKE_SESSION_ID)
    -        assert shelve == {}
    -        assert shelve._location == shelve_path / FAKE_SESSION_ID / "store"
    -        assert not shelve_path.exists()
    -
    -    def test_save(self, shelve_path):
    -        assert not shelve_path.exists()
    -
    -        shelve = ShelveStore(str(shelve_path), FAKE_SESSION_ID)
    -        shelve["shelve_path"] = shelve_path
    -        shelve.save()
    -
    -        assert (shelve_path / FAKE_SESSION_ID).is_dir()
    -
    -        reloaded = ShelveStore(str(shelve_path), FAKE_SESSION_ID)
    -        assert reloaded == {"shelve_path": shelve_path}
    -
    -    def test_update(self, shelve_path):
    -        shelve = ShelveStore(str(shelve_path), FAKE_SESSION_ID)
    -        shelve["shelve_path"] = shelve_path
    -        shelve.save()
    -
    -        shelve.update(new_key="new_value")
    -        del shelve["shelve_path"]
    -        reloaded = ShelveStore(str(shelve_path), FAKE_SESSION_ID)
    -        assert reloaded == {"shelve_path": shelve_path}  # changes not saved yet
    -
    -        shelve.save()
    -        reloaded = ShelveStore(str(shelve_path), FAKE_SESSION_ID)
    -        assert reloaded == {"new_key": "new_value"}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.