From 3a96b7da692f3b0936dc26451ddd947c8f1f4f09 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Fri, 15 May 2026 07:08:17 -0400 Subject: [PATCH 01/29] Fix missing \$PYTHON_MGR_CMD in --use-uv branches Three sites used \$PYTHON_MGR directly (or hardcoded 'run') instead of \$PYTHON_MGR \$PYTHON_MGR_CMD, breaking the uv code path: - install_yara_python: uv pip install yara-python - install_CAPE: uv pip install -r pyproject.toml - install_volatility3: hardcoded 'run' instead of \$PYTHON_MGR_CMD --- installer/cape2.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/installer/cape2.sh b/installer/cape2.sh index 61e8c28c6d8..e78ff2a9282 100755 --- a/installer/cape2.sh +++ b/installer/cape2.sh @@ -805,7 +805,7 @@ function install_yara_python() { # Install from PyPI if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then - sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR pip install yara-python \ + sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR $PYTHON_MGR_CMD pip install yara-python \ --no-binary :all: \ --config-settings=\"--global-option=build\" \ --config-settings=\"--global-option=--enable-cuckoo\" \ @@ -1380,7 +1380,7 @@ function install_CAPE() { echo "[-] pyproject.toml not found in $CAPE_ROOT" return fi - sudo -u ${USER} bash -c "export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring; CRYPTOGRAPHY_DONT_BUILD_RUST=1 $PYTHON_MGR pip install -r pyproject.toml" + sudo -u ${USER} bash -c "export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring; CRYPTOGRAPHY_DONT_BUILD_RUST=1 $PYTHON_MGR $PYTHON_MGR_CMD pip install -r pyproject.toml" if [ "$DISABLE_LIBVIRT" -eq 0 ]; then # Integrated libvirt install @@ -1537,7 +1537,7 @@ function install_volatility3() { sudo apt-get install -y unzip if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR $PYTHON_MGR_CMD pip install git+https://github.com/volatilityfoundation/volatility3" - vol_path=$(sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR run python3 -c \"import volatility3.plugins;print(volatility3.__file__.replace('__init__.py', 'symbols/'))\"") + vol_path=$(sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR $PYTHON_MGR_CMD python3 -c \"import volatility3.plugins;print(volatility3.__file__.replace('__init__.py', 'symbols/'))\"") else sudo -u ${USER} $PYTHON_MGR $PYTHON_MGR_CMD pip3 install git+https://github.com/volatilityfoundation/volatility3 vol_path=$(sudo -u ${USER} $PYTHON_MGR $PYTHON_MGR_CMD python3 -c "import volatility3.plugins;print(volatility3.__file__.replace('__init__.py', 'symbols/'))") From 1f900c52279360e3206b210be4537c54b9c24a45 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Fri, 15 May 2026 07:15:21 -0400 Subject: [PATCH 02/29] Fix uv dep install: use uv sync and uv pip install correctly - install_CAPE: replace uv pip install -r pyproject.toml with uv sync --no-install-project; the former fails because poetry-core refuses to build an editable package when package-mode=false - install_yara_python: drop $PYTHON_MGR_CMD so uv uses uv pip install rather than uv run pip install; uv run triggers a project sync (and the same build failure) before running any command --- installer/cape2.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/installer/cape2.sh b/installer/cape2.sh index e78ff2a9282..ab567b5cc98 100755 --- a/installer/cape2.sh +++ b/installer/cape2.sh @@ -805,7 +805,7 @@ function install_yara_python() { # Install from PyPI if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then - sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR $PYTHON_MGR_CMD pip install yara-python \ + sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR pip install yara-python \ --no-binary :all: \ --config-settings=\"--global-option=build\" \ --config-settings=\"--global-option=--enable-cuckoo\" \ @@ -1380,7 +1380,11 @@ function install_CAPE() { echo "[-] pyproject.toml not found in $CAPE_ROOT" return fi - sudo -u ${USER} bash -c "export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring; CRYPTOGRAPHY_DONT_BUILD_RUST=1 $PYTHON_MGR $PYTHON_MGR_CMD pip install -r pyproject.toml" + if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then + sudo -u ${USER} bash -c "cd $CAPE_ROOT && export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring; $PYTHON_MGR sync --no-install-project" + else + sudo -u ${USER} bash -c "export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring; CRYPTOGRAPHY_DONT_BUILD_RUST=1 $PYTHON_MGR $PYTHON_MGR_CMD pip install -r pyproject.toml" + fi if [ "$DISABLE_LIBVIRT" -eq 0 ]; then # Integrated libvirt install From c098f9bf23f56d9d30f8f948876e481fa1ddfd74 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Fri, 15 May 2026 07:34:22 -0400 Subject: [PATCH 03/29] Refactor pip install abstraction for uv and poetry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add PYTHON_MGR_PIP ("run pip" / "pip") so $PYTHON_MGR $PYTHON_MGR_PIP install X resolves to the correct subcommand for each manager — poetry uses "run pip" while uv uses "pip" directly (uv run pip install triggers a project sync that fails on package-mode=false projects). Rename PYTHON_MGR_INSTALL -> PYTHON_MGR_INSTALL_PYPROJECT and fix its uv value from "" (no-op) to "sync --no-install-project", enabling install_CAPE() and install_guacamole() to collapse their USE_UV branches into a single line. Also collapse four other USE_UV pip install branches (distributed, yara-python, libvirt, capa) into unconditional lines using $PYTHON_MGR_PIP, and fix a pip3 typo in install_volatility3(). --- installer/cape2.sh | 66 +++++++++++++--------------------------------- 1 file changed, 19 insertions(+), 47 deletions(-) diff --git a/installer/cape2.sh b/installer/cape2.sh index ab567b5cc98..c0f567a4c01 100755 --- a/installer/cape2.sh +++ b/installer/cape2.sh @@ -66,7 +66,8 @@ CAPE_ROOT="${CAPE_ROOT:-/opt/CAPEv2}" USE_UV=${USE_UV:-false} PYTHON_MGR="/etc/poetry/bin/poetry" PYTHON_MGR_CMD="run" -PYTHON_MGR_INSTALL="install" +PYTHON_MGR_PIP="run pip" +PYTHON_MGR_INSTALL_PYPROJECT="install" # if a config file is present, read it in if [ -f "./cape-config.sh" ]; then @@ -666,11 +667,7 @@ function redsocks2() { function distributed() { echo "[+] Configure distributed configuration" sudo apt-get install -y uwsgi uwsgi-plugin-python3 nginx 2>/dev/null - if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then - sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR $PYTHON_MGR_CMD pip install flask flask-restful flask-sqlalchemy requests" - else - sudo -u ${USER} bash -c "$PYTHON_MGR $PYTHON_MGR_CMD pip install flask flask-restful flask-sqlalchemy requests" - fi + sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR $PYTHON_MGR_PIP install flask flask-restful flask-sqlalchemy requests" sudo cp $CAPE_ROOT/uwsgi/capedist.ini /etc/uwsgi/apps-available/cape_dist.ini sudo ln -s /etc/uwsgi/apps-available/cape_dist.ini /etc/uwsgi/apps-enabled @@ -788,7 +785,7 @@ function install_yara_x() { sudo -u ${USER} git clone https://github.com/VirusTotal/yara-x cd yara-x || return sudo -u ${USER} bash -c 'source "$HOME/.cargo/env" ; cargo install --path cli' - sudo -u ${USER} $PYTHON_MGR --directory $CAPE_ROOT/ $PYTHON_MGR_CMD pip install yara-x + sudo -u ${USER} $PYTHON_MGR --directory $CAPE_ROOT/ $PYTHON_MGR_PIP install yara-x } function install_yara_python() { @@ -804,21 +801,12 @@ function install_yara_python() { # This replaces the legacy setup.py build approach # Install from PyPI - if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then - sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR pip install yara-python \ - --no-binary :all: \ - --config-settings=\"--global-option=build\" \ - --config-settings=\"--global-option=--enable-cuckoo\" \ - --config-settings=\"--global-option=--enable-magic\" \ - --config-settings=\"--global-option=--enable-profiling\"" - else - sudo -u ${USER} $PYTHON_MGR --directory $CAPE_ROOT $PYTHON_MGR_CMD pip install yara-python \ - --no-binary :all: \ - --config-settings="--global-option=build" \ - --config-settings="--global-option=--enable-cuckoo" \ - --config-settings="--global-option=--enable-magic" \ - --config-settings="--global-option=--enable-profiling" - fi + sudo -u ${USER} $PYTHON_MGR --directory $CAPE_ROOT $PYTHON_MGR_PIP install yara-python \ + --no-binary :all: \ + --config-settings="--global-option=build" \ + --config-settings="--global-option=--enable-cuckoo" \ + --config-settings="--global-option=--enable-magic" \ + --config-settings="--global-option=--enable-profiling" # Install from local source (commented out) # sudo -u ${USER} $PYTHON_MGR --directory $CAPE_ROOT $PYTHON_MGR_CMD pip install /tmp/yara-python \ @@ -909,16 +897,7 @@ function install_libvirt() { export_path="${temp_export_path%/*}/" export PKG_CONFIG_PATH=$export_path - # Run build and install within the project environment - # We use sudo -u cape ... to install into the user's environment managed by poetry/uv/pip - if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then - # sudo -u ${USER} bash -c "export PKG_CONFIG_PATH=$export_path; cd $CAPE_ROOT && $PYTHON_MGR pip install /tmp/libvirt-python-${LIB_VERSION}" - sudo -u ${USER} bash -c "export PKG_CONFIG_PATH=$export_path; cd $CAPE_ROOT && $PYTHON_MGR pip install libvirt-python==${LIB_VERSION}" - elif [ "$PYTHON_MGR" = "/etc/poetry/bin/poetry" ]; then - sudo -u ${USER} bash -c "export PKG_CONFIG_PATH=$export_path; $PYTHON_MGR --directory $CAPE_ROOT $PYTHON_MGR_CMD pip install libvirt-python==${LIB_VERSION}" - else - sudo -u ${USER} bash -c "export PKG_CONFIG_PATH=$export_path; pip3 install libvirt-python==${LIB_VERSION}" - fi + sudo -u ${USER} bash -c "export PKG_CONFIG_PATH=$export_path; $PYTHON_MGR --directory $CAPE_ROOT $PYTHON_MGR_PIP install libvirt-python==${LIB_VERSION}" } function install_mongo(){ @@ -1061,11 +1040,7 @@ function install_capa() { cd capa || return git pull git submodule update --init rules - if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then - sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR $PYTHON_MGR_CMD pip install /tmp/capa" - else - sudo -u ${USER} $PYTHON_MGR --directory $CAPE_ROOT/ $PYTHON_MGR_CMD pip install /tmp/capa - fi + sudo -u ${USER} $PYTHON_MGR --directory $CAPE_ROOT/ $PYTHON_MGR_PIP install /tmp/capa cd $CAPE_ROOT if [ -d /tmp/capa ]; then sudo rm -rf /tmp/capa @@ -1380,11 +1355,7 @@ function install_CAPE() { echo "[-] pyproject.toml not found in $CAPE_ROOT" return fi - if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then - sudo -u ${USER} bash -c "cd $CAPE_ROOT && export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring; $PYTHON_MGR sync --no-install-project" - else - sudo -u ${USER} bash -c "export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring; CRYPTOGRAPHY_DONT_BUILD_RUST=1 $PYTHON_MGR $PYTHON_MGR_CMD pip install -r pyproject.toml" - fi + sudo -u ${USER} bash -c "cd $CAPE_ROOT && export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring; $PYTHON_MGR $PYTHON_MGR_INSTALL_PYPROJECT" if [ "$DISABLE_LIBVIRT" -eq 0 ]; then # Integrated libvirt install @@ -1540,10 +1511,10 @@ function install_volatility3() { echo "[+] Installing volatility3" sudo apt-get install -y unzip if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then - sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR $PYTHON_MGR_CMD pip install git+https://github.com/volatilityfoundation/volatility3" + sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR $PYTHON_MGR_PIP install git+https://github.com/volatilityfoundation/volatility3" vol_path=$(sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR $PYTHON_MGR_CMD python3 -c \"import volatility3.plugins;print(volatility3.__file__.replace('__init__.py', 'symbols/'))\"") else - sudo -u ${USER} $PYTHON_MGR $PYTHON_MGR_CMD pip3 install git+https://github.com/volatilityfoundation/volatility3 + sudo -u ${USER} $PYTHON_MGR $PYTHON_MGR_PIP install git+https://github.com/volatilityfoundation/volatility3 vol_path=$(sudo -u ${USER} $PYTHON_MGR $PYTHON_MGR_CMD python3 -c "import volatility3.plugins;print(volatility3.__file__.replace('__init__.py', 'symbols/'))") fi @@ -1633,7 +1604,7 @@ function install_guacamole() { sudo usermod www-data -G ${USER} cd $CAPE_ROOT - sudo -u ${USER} bash -c "export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring; ${poetry_path} $PYTHON_MGR_INSTALL" + sudo -u ${USER} bash -c "cd $CAPE_ROOT && export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring; ${poetry_path} $PYTHON_MGR_INSTALL_PYPROJECT" cd .. systemctl daemon-reload @@ -1767,7 +1738,8 @@ for i in "$@"; do USE_UV="true" PYTHON_MGR="/usr/local/bin/uv" PYTHON_MGR_CMD="run" - PYTHON_MGR_INSTALL="" + PYTHON_MGR_PIP="pip" + PYTHON_MGR_INSTALL_PYPROJECT="sync --no-install-project" fi done @@ -1822,7 +1794,7 @@ case "$COMMAND" in fi # Update FLARE CAPA rules once per day if ! crontab -l | grep -q 'community.py -waf -cr'; then - crontab -l | { cat; echo "5 0 */1 * * cd $CAPE_ROOT/utils/ && sudo -u ${USER} $PYTHON_MGR --directory $CAPE_ROOT/ $PYTHON_MGR_CMD python3 community.py -waf -cr && sudo -u ${USER} $PYTHON_MGR --directory $CAPE_ROOT/ $PYTHON_MGR_CMD pip install -U flare-capa && systemctl restart cape-processor 2>/dev/null"; } | crontab - + crontab -l | { cat; echo "5 0 */1 * * cd $CAPE_ROOT/utils/ && sudo -u ${USER} $PYTHON_MGR --directory $CAPE_ROOT/ $PYTHON_MGR_CMD python3 community.py -waf -cr && sudo -u ${USER} $PYTHON_MGR --directory $CAPE_ROOT/ $PYTHON_MGR_PIP install -U flare-capa && systemctl restart cape-processor 2>/dev/null"; } | crontab - fi install_librenms if [ "$clamav_enable" -ge 1 ]; then From d3b7cc4e3f4420c7c8e4d691eaa462d604ca1a5d Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Fri, 15 May 2026 08:40:19 -0400 Subject: [PATCH 04/29] Fix uv service startup: swap sed order and mark project as non-package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs broke CAPE services on boot with --use-uv: 1. cape-rooter ExecStartPre ran `uv config cache-dir ...` because the removal sed ran after the poetry→uv replacement, so its pattern (/poetry ) no longer matched. Fix: remove ExecStartPre lines first. 2. All services failed with build_editable because uv tried to install CAPEv2 as an editable package; poetry-core refused due to package-mode = false. Fix: add [tool.uv] package = false so uv natively skips the install step. --- .gitignore | 1 + installer/cape2.sh | 7 +- pyproject.toml | 3 + uv.lock | 534 +-------------------------------------------- 4 files changed, 17 insertions(+), 528 deletions(-) diff --git a/.gitignore b/.gitignore index ff14b51a369..6674d767513 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ installer/kvm-config.sh docs/book/src/_build /.vs +.venv diff --git a/installer/cape2.sh b/installer/cape2.sh index c0f567a4c01..9371d75a0ec 100755 --- a/installer/cape2.sh +++ b/installer/cape2.sh @@ -1439,11 +1439,12 @@ function install_systemd() { fi if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then - sed -i "s|/etc/poetry/bin/poetry|$PYTHON_MGR|g" /lib/systemd/system/cape*.service - sed -i "s|/etc/poetry/bin/poetry|$PYTHON_MGR|g" /lib/systemd/system/guac*.service - # remove poetry config commands as uv does not have them or needs them + # Remove poetry config ExecStartPre lines BEFORE replacing poetry→uv so the + # pattern still matches (after replacement the path no longer contains /poetry) sed -i "s|^ExecStartPre=.*/poetry .*||g" /lib/systemd/system/cape-fstab.service || true sed -i "s|^ExecStartPre=.*/poetry .*||g" /lib/systemd/system/cape-rooter.service || true + sed -i "s|/etc/poetry/bin/poetry|$PYTHON_MGR|g" /lib/systemd/system/cape*.service + sed -i "s|/etc/poetry/bin/poetry|$PYTHON_MGR|g" /lib/systemd/system/guac*.service fi systemctl daemon-reload diff --git a/pyproject.toml b/pyproject.toml index d313144fd6b..c604605cea4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -118,6 +118,9 @@ pre-commit = ">=2.19.0" [tool.poetry] package-mode = false +[tool.uv] +package = false + [tool.black] line-length = 132 include = "\\.py(_disabled)?$" diff --git a/uv.lock b/uv.lock index 77f521d020a..d887de7b14c 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.10, <4.0" resolution-markers = [ "python_full_version >= '3.14' and platform_python_implementation == 'CPython'", @@ -269,50 +269,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, ] -[[package]] -name = "black" -version = "26.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, - { name = "pytokens" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e1/c5/61175d618685d42b005847464b8fb4743a67b1b8fdb75e50e5a96c31a27a/black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07", size = 666155, upload-time = "2026-03-12T03:36:03.593Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/a8/11170031095655d36ebc6664fe0897866f6023892396900eec0e8fdc4299/black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2", size = 1866562, upload-time = "2026-03-12T03:39:58.639Z" }, - { url = "https://files.pythonhosted.org/packages/69/ce/9e7548d719c3248c6c2abfd555d11169457cbd584d98d179111338423790/black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b", size = 1703623, upload-time = "2026-03-12T03:40:00.347Z" }, - { url = "https://files.pythonhosted.org/packages/7f/0a/8d17d1a9c06f88d3d030d0b1d4373c1551146e252afe4547ed601c0e697f/black-26.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c54a4a82e291a1fee5137371ab488866b7c86a3305af4026bdd4dc78642e1ac", size = 1768388, upload-time = "2026-03-12T03:40:01.765Z" }, - { url = "https://files.pythonhosted.org/packages/52/79/c1ee726e221c863cde5164f925bacf183dfdf0397d4e3f94889439b947b4/black-26.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:6e131579c243c98f35bce64a7e08e87fb2d610544754675d4a0e73a070a5aa3a", size = 1412969, upload-time = "2026-03-12T03:40:03.252Z" }, - { url = "https://files.pythonhosted.org/packages/73/a5/15c01d613f5756f68ed8f6d4ec0a1e24b82b18889fa71affd3d1f7fad058/black-26.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:5ed0ca58586c8d9a487352a96b15272b7fa55d139fc8496b519e78023a8dab0a", size = 1220345, upload-time = "2026-03-12T03:40:04.892Z" }, - { url = "https://files.pythonhosted.org/packages/17/57/5f11c92861f9c92eb9dddf515530bc2d06db843e44bdcf1c83c1427824bc/black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff", size = 1851987, upload-time = "2026-03-12T03:40:06.248Z" }, - { url = "https://files.pythonhosted.org/packages/54/aa/340a1463660bf6831f9e39646bf774086dbd8ca7fc3cded9d59bbdf4ad0a/black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c", size = 1689499, upload-time = "2026-03-12T03:40:07.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/01/b726c93d717d72733da031d2de10b92c9fa4c8d0c67e8a8a372076579279/black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5", size = 1754369, upload-time = "2026-03-12T03:40:09.279Z" }, - { url = "https://files.pythonhosted.org/packages/e3/09/61e91881ca291f150cfc9eb7ba19473c2e59df28859a11a88248b5cbbc4d/black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e", size = 1413613, upload-time = "2026-03-12T03:40:10.943Z" }, - { url = "https://files.pythonhosted.org/packages/16/73/544f23891b22e7efe4d8f812371ab85b57f6a01b2fc45e3ba2e52ba985b8/black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5", size = 1219719, upload-time = "2026-03-12T03:40:12.597Z" }, - { url = "https://files.pythonhosted.org/packages/dc/f8/da5eae4fc75e78e6dceb60624e1b9662ab00d6b452996046dfa9b8a6025b/black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1", size = 1895920, upload-time = "2026-03-12T03:40:13.921Z" }, - { url = "https://files.pythonhosted.org/packages/2c/9f/04e6f26534da2e1629b2b48255c264cabf5eedc5141d04516d9d68a24111/black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f", size = 1718499, upload-time = "2026-03-12T03:40:15.239Z" }, - { url = "https://files.pythonhosted.org/packages/04/91/a5935b2a63e31b331060c4a9fdb5a6c725840858c599032a6f3aac94055f/black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7", size = 1794994, upload-time = "2026-03-12T03:40:17.124Z" }, - { url = "https://files.pythonhosted.org/packages/e7/0a/86e462cdd311a3c2a8ece708d22aba17d0b2a0d5348ca34b40cdcbea512e/black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983", size = 1420867, upload-time = "2026-03-12T03:40:18.83Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e5/22515a19cb7eaee3440325a6b0d95d2c0e88dd180cb011b12ae488e031d1/black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb", size = 1230124, upload-time = "2026-03-12T03:40:20.425Z" }, - { url = "https://files.pythonhosted.org/packages/f5/77/5728052a3c0450c53d9bb3945c4c46b91baa62b2cafab6801411b6271e45/black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54", size = 1895034, upload-time = "2026-03-12T03:40:21.813Z" }, - { url = "https://files.pythonhosted.org/packages/52/73/7cae55fdfdfbe9d19e9a8d25d145018965fe2079fa908101c3733b0c55a0/black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f", size = 1718503, upload-time = "2026-03-12T03:40:23.666Z" }, - { url = "https://files.pythonhosted.org/packages/e1/87/af89ad449e8254fdbc74654e6467e3c9381b61472cc532ee350d28cfdafb/black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56", size = 1793557, upload-time = "2026-03-12T03:40:25.497Z" }, - { url = "https://files.pythonhosted.org/packages/43/10/d6c06a791d8124b843bf325ab4ac7d2f5b98731dff84d6064eafd687ded1/black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839", size = 1422766, upload-time = "2026-03-12T03:40:27.14Z" }, - { url = "https://files.pythonhosted.org/packages/59/4f/40a582c015f2d841ac24fed6390bd68f0fc896069ff3a886317959c9daf8/black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2", size = 1232140, upload-time = "2026-03-12T03:40:28.882Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/e36e27c9cebc1311b7579210df6f1c86e50f2d7143ae4fcf8a5017dc8809/black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78", size = 1889234, upload-time = "2026-03-12T03:40:30.964Z" }, - { url = "https://files.pythonhosted.org/packages/0e/7b/9871acf393f64a5fa33668c19350ca87177b181f44bb3d0c33b2d534f22c/black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568", size = 1720522, upload-time = "2026-03-12T03:40:32.346Z" }, - { url = "https://files.pythonhosted.org/packages/03/87/e766c7f2e90c07fb7586cc787c9ae6462b1eedab390191f2b7fc7f6170a9/black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f", size = 1787824, upload-time = "2026-03-12T03:40:33.636Z" }, - { url = "https://files.pythonhosted.org/packages/ac/94/2424338fb2d1875e9e83eed4c8e9c67f6905ec25afd826a911aea2b02535/black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c", size = 1445855, upload-time = "2026-03-12T03:40:35.442Z" }, - { url = "https://files.pythonhosted.org/packages/86/43/0c3338bd928afb8ee7471f1a4eec3bdbe2245ccb4a646092a222e8669840/black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1", size = 1258109, upload-time = "2026-03-12T03:40:36.832Z" }, - { url = "https://files.pythonhosted.org/packages/8e/0d/52d98722666d6fc6c3dd4c76df339501d6efd40e0ff95e6186a7b7f0befd/black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b", size = 207542, upload-time = "2026-03-12T03:36:01.668Z" }, -] - [[package]] name = "bs4" version = "0.0.1" @@ -466,25 +422,8 @@ mcp = [ { name = "fastmcp" }, { name = "httpx" }, ] - -[package.dev-dependencies] -dev = [ - { name = "black" }, - { name = "func-timeout" }, - { name = "httpretty" }, - { name = "isort" }, - { name = "mypy" }, - { name = "pre-commit" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "pytest-cov" }, - { name = "pytest-django" }, - { name = "pytest-freezer" }, - { name = "pytest-mock" }, - { name = "pytest-pretty" }, - { name = "pytest-xdist" }, - { name = "tenacity" }, - { name = "types-requests" }, +yara = [ + { name = "plyara" }, ] [package.metadata] @@ -533,6 +472,7 @@ requires-dist = [ { name = "peepdf-3", specifier = "==5.0.0" }, { name = "pefile" }, { name = "pillow", specifier = ">=8.2.0" }, + { name = "plyara", marker = "extra == 'yara'" }, { name = "psutil", specifier = "==6.1.1" }, { name = "psycopg2-binary", specifier = ">=2.9.10" }, { name = "pycryptodomex", specifier = ">=3.20.0" }, @@ -560,27 +500,7 @@ requires-dist = [ { name = "werkzeug", specifier = "==3.1.5" }, { name = "yara-python", specifier = "==4.5.1" }, ] -provides-extras = ["maco", "gcp", "mcp"] - -[package.metadata.requires-dev] -dev = [ - { name = "black", specifier = ">=24.3.0" }, - { name = "func-timeout", specifier = ">=4.3.5" }, - { name = "httpretty", specifier = ">=1.1.4" }, - { name = "isort", specifier = ">=5.10.1" }, - { name = "mypy", specifier = "==1.14.1" }, - { name = "pre-commit", specifier = ">=2.19.0" }, - { name = "pytest", specifier = "==7.2.2" }, - { name = "pytest-asyncio", specifier = "==0.18.3" }, - { name = "pytest-cov", specifier = "==3.0.0" }, - { name = "pytest-django", specifier = "==4.5.2" }, - { name = "pytest-freezer", specifier = "==0.4.8" }, - { name = "pytest-mock", specifier = "==3.7.0" }, - { name = "pytest-pretty", specifier = "==1.1.0" }, - { name = "pytest-xdist", specifier = "==3.6.1" }, - { name = "tenacity", specifier = "==8.1.0" }, - { name = "types-requests", specifier = ">=2.32" }, -] +provides-extras = ["maco", "gcp", "yara", "mcp"] [[package]] name = "capstone" @@ -746,15 +666,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, ] -[[package]] -name = "cfgv" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, -] - [[package]] name = "channels" version = "4.3.2" @@ -911,110 +822,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/40/c199d095151addf69efdb4b9ca3a4f20f70e20508d6222bffb9b76f58573/constantly-23.10.4-py3-none-any.whl", hash = "sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9", size = 13547, upload-time = "2023-10-28T23:18:23.038Z" }, ] -[[package]] -name = "coverage" -version = "7.13.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/9a/3742e58fd04b233df95c012ee9f3dfe04708a5e1d32613bd2d47d4e1be0d/coverage-7.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e1fa280b3ad78eea5be86f94f461c04943d942697e0dac889fa18fff8f5f9147", size = 218633, upload-time = "2025-12-28T15:40:10.165Z" }, - { url = "https://files.pythonhosted.org/packages/7e/45/7e6bdc94d89cd7c8017ce735cf50478ddfe765d4fbf0c24d71d30ea33d7a/coverage-7.13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c3d8c679607220979434f494b139dfb00131ebf70bb406553d69c1ff01a5c33d", size = 219147, upload-time = "2025-12-28T15:40:12.069Z" }, - { url = "https://files.pythonhosted.org/packages/f7/38/0d6a258625fd7f10773fe94097dc16937a5f0e3e0cdf3adef67d3ac6baef/coverage-7.13.1-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339dc63b3eba969067b00f41f15ad161bf2946613156fb131266d8debc8e44d0", size = 245894, upload-time = "2025-12-28T15:40:13.556Z" }, - { url = "https://files.pythonhosted.org/packages/27/58/409d15ea487986994cbd4d06376e9860e9b157cfbfd402b1236770ab8dd2/coverage-7.13.1-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:db622b999ffe49cb891f2fff3b340cdc2f9797d01a0a202a0973ba2562501d90", size = 247721, upload-time = "2025-12-28T15:40:15.37Z" }, - { url = "https://files.pythonhosted.org/packages/da/bf/6e8056a83fd7a96c93341f1ffe10df636dd89f26d5e7b9ca511ce3bcf0df/coverage-7.13.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1443ba9acbb593fa7c1c29e011d7c9761545fe35e7652e85ce7f51a16f7e08d", size = 249585, upload-time = "2025-12-28T15:40:17.226Z" }, - { url = "https://files.pythonhosted.org/packages/f4/15/e1daff723f9f5959acb63cbe35b11203a9df77ee4b95b45fffd38b318390/coverage-7.13.1-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c832ec92c4499ac463186af72f9ed4d8daec15499b16f0a879b0d1c8e5cf4a3b", size = 246597, upload-time = "2025-12-28T15:40:19.028Z" }, - { url = "https://files.pythonhosted.org/packages/74/a6/1efd31c5433743a6ddbc9d37ac30c196bb07c7eab3d74fbb99b924c93174/coverage-7.13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:562ec27dfa3f311e0db1ba243ec6e5f6ab96b1edfcfc6cf86f28038bc4961ce6", size = 247626, upload-time = "2025-12-28T15:40:20.846Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9f/1609267dd3e749f57fdd66ca6752567d1c13b58a20a809dc409b263d0b5f/coverage-7.13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4de84e71173d4dada2897e5a0e1b7877e5eefbfe0d6a44edee6ce31d9b8ec09e", size = 245629, upload-time = "2025-12-28T15:40:22.397Z" }, - { url = "https://files.pythonhosted.org/packages/e2/f6/6815a220d5ec2466383d7cc36131b9fa6ecbe95c50ec52a631ba733f306a/coverage-7.13.1-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:a5a68357f686f8c4d527a2dc04f52e669c2fc1cbde38f6f7eb6a0e58cbd17cae", size = 245901, upload-time = "2025-12-28T15:40:23.836Z" }, - { url = "https://files.pythonhosted.org/packages/ac/58/40576554cd12e0872faf6d2c0eb3bc85f71d78427946ddd19ad65201e2c0/coverage-7.13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:77cc258aeb29a3417062758975521eae60af6f79e930d6993555eeac6a8eac29", size = 246505, upload-time = "2025-12-28T15:40:25.421Z" }, - { url = "https://files.pythonhosted.org/packages/3b/77/9233a90253fba576b0eee81707b5781d0e21d97478e5377b226c5b096c0f/coverage-7.13.1-cp310-cp310-win32.whl", hash = "sha256:bb4f8c3c9a9f34423dba193f241f617b08ffc63e27f67159f60ae6baf2dcfe0f", size = 221257, upload-time = "2025-12-28T15:40:27.217Z" }, - { url = "https://files.pythonhosted.org/packages/e0/43/e842ff30c1a0a623ec80db89befb84a3a7aad7bfe44a6ea77d5a3e61fedd/coverage-7.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:c8e2706ceb622bc63bac98ebb10ef5da80ed70fbd8a7999a5076de3afaef0fb1", size = 222191, upload-time = "2025-12-28T15:40:28.916Z" }, - { url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" }, - { url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" }, - { url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" }, - { url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" }, - { url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" }, - { url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" }, - { url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" }, - { url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" }, - { url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" }, - { url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" }, - { url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" }, - { url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" }, - { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" }, - { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" }, - { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" }, - { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" }, - { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" }, - { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" }, - { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" }, - { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" }, - { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" }, - { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" }, - { url = "https://files.pythonhosted.org/packages/a3/a4/e98e689347a1ff1a7f67932ab535cef82eb5e78f32a9e4132e114bbb3a0a/coverage-7.13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cb237bfd0ef4d5eb6a19e29f9e528ac67ac3be932ea6b44fb6cc09b9f3ecff78", size = 218951, upload-time = "2025-12-28T15:41:16.653Z" }, - { url = "https://files.pythonhosted.org/packages/32/33/7cbfe2bdc6e2f03d6b240d23dc45fdaf3fd270aaf2d640be77b7f16989ab/coverage-7.13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1dcb645d7e34dcbcc96cd7c132b1fc55c39263ca62eb961c064eb3928997363b", size = 219325, upload-time = "2025-12-28T15:41:18.609Z" }, - { url = "https://files.pythonhosted.org/packages/59/f6/efdabdb4929487baeb7cb2a9f7dac457d9356f6ad1b255be283d58b16316/coverage-7.13.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3d42df8201e00384736f0df9be2ced39324c3907607d17d50d50116c989d84cd", size = 250309, upload-time = "2025-12-28T15:41:20.629Z" }, - { url = "https://files.pythonhosted.org/packages/12/da/91a52516e9d5aea87d32d1523f9cdcf7a35a3b298e6be05d6509ba3cfab2/coverage-7.13.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fa3edde1aa8807de1d05934982416cb3ec46d1d4d91e280bcce7cca01c507992", size = 252907, upload-time = "2025-12-28T15:41:22.257Z" }, - { url = "https://files.pythonhosted.org/packages/75/38/f1ea837e3dc1231e086db1638947e00d264e7e8c41aa8ecacf6e1e0c05f4/coverage-7.13.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9edd0e01a343766add6817bc448408858ba6b489039eaaa2018474e4001651a4", size = 254148, upload-time = "2025-12-28T15:41:23.87Z" }, - { url = "https://files.pythonhosted.org/packages/7f/43/f4f16b881aaa34954ba446318dea6b9ed5405dd725dd8daac2358eda869a/coverage-7.13.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:985b7836931d033570b94c94713c6dba5f9d3ff26045f72c3e5dbc5fe3361e5a", size = 250515, upload-time = "2025-12-28T15:41:25.437Z" }, - { url = "https://files.pythonhosted.org/packages/84/34/8cba7f00078bd468ea914134e0144263194ce849ec3baad187ffb6203d1c/coverage-7.13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ffed1e4980889765c84a5d1a566159e363b71d6b6fbaf0bebc9d3c30bc016766", size = 252292, upload-time = "2025-12-28T15:41:28.459Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a4/cffac66c7652d84ee4ac52d3ccb94c015687d3b513f9db04bfcac2ac800d/coverage-7.13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8842af7f175078456b8b17f1b73a0d16a65dcbdc653ecefeb00a56b3c8c298c4", size = 250242, upload-time = "2025-12-28T15:41:30.02Z" }, - { url = "https://files.pythonhosted.org/packages/f4/78/9a64d462263dde416f3c0067efade7b52b52796f489b1037a95b0dc389c9/coverage-7.13.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:ccd7a6fca48ca9c131d9b0a2972a581e28b13416fc313fb98b6d24a03ce9a398", size = 250068, upload-time = "2025-12-28T15:41:32.007Z" }, - { url = "https://files.pythonhosted.org/packages/69/c8/a8994f5fece06db7c4a97c8fc1973684e178599b42e66280dded0524ef00/coverage-7.13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0403f647055de2609be776965108447deb8e384fe4a553c119e3ff6bfbab4784", size = 251846, upload-time = "2025-12-28T15:41:33.946Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f7/91fa73c4b80305c86598a2d4e54ba22df6bf7d0d97500944af7ef155d9f7/coverage-7.13.1-cp313-cp313-win32.whl", hash = "sha256:549d195116a1ba1e1ae2f5ca143f9777800f6636eab917d4f02b5310d6d73461", size = 221512, upload-time = "2025-12-28T15:41:35.519Z" }, - { url = "https://files.pythonhosted.org/packages/45/0b/0768b4231d5a044da8f75e097a8714ae1041246bb765d6b5563bab456735/coverage-7.13.1-cp313-cp313-win_amd64.whl", hash = "sha256:5899d28b5276f536fcf840b18b61a9fce23cc3aec1d114c44c07fe94ebeaa500", size = 222321, upload-time = "2025-12-28T15:41:37.371Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b8/bdcb7253b7e85157282450262008f1366aa04663f3e3e4c30436f596c3e2/coverage-7.13.1-cp313-cp313-win_arm64.whl", hash = "sha256:868a2fae76dfb06e87291bcbd4dcbcc778a8500510b618d50496e520bd94d9b9", size = 220949, upload-time = "2025-12-28T15:41:39.553Z" }, - { url = "https://files.pythonhosted.org/packages/70/52/f2be52cc445ff75ea8397948c96c1b4ee14f7f9086ea62fc929c5ae7b717/coverage-7.13.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:67170979de0dacac3f3097d02b0ad188d8edcea44ccc44aaa0550af49150c7dc", size = 219643, upload-time = "2025-12-28T15:41:41.567Z" }, - { url = "https://files.pythonhosted.org/packages/47/79/c85e378eaa239e2edec0c5523f71542c7793fe3340954eafb0bc3904d32d/coverage-7.13.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f80e2bb21bfab56ed7405c2d79d34b5dc0bc96c2c1d2a067b643a09fb756c43a", size = 219997, upload-time = "2025-12-28T15:41:43.418Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9b/b1ade8bfb653c0bbce2d6d6e90cc6c254cbb99b7248531cc76253cb4da6d/coverage-7.13.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f83351e0f7dcdb14d7326c3d8d8c4e915fa685cbfdc6281f9470d97a04e9dfe4", size = 261296, upload-time = "2025-12-28T15:41:45.207Z" }, - { url = "https://files.pythonhosted.org/packages/1f/af/ebf91e3e1a2473d523e87e87fd8581e0aa08741b96265730e2d79ce78d8d/coverage-7.13.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb3f6562e89bad0110afbe64e485aac2462efdce6232cdec7862a095dc3412f6", size = 263363, upload-time = "2025-12-28T15:41:47.163Z" }, - { url = "https://files.pythonhosted.org/packages/c4/8b/fb2423526d446596624ac7fde12ea4262e66f86f5120114c3cfd0bb2befa/coverage-7.13.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77545b5dcda13b70f872c3b5974ac64c21d05e65b1590b441c8560115dc3a0d1", size = 265783, upload-time = "2025-12-28T15:41:49.03Z" }, - { url = "https://files.pythonhosted.org/packages/9b/26/ef2adb1e22674913b89f0fe7490ecadcef4a71fa96f5ced90c60ec358789/coverage-7.13.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4d240d260a1aed814790bbe1f10a5ff31ce6c21bc78f0da4a1e8268d6c80dbd", size = 260508, upload-time = "2025-12-28T15:41:51.035Z" }, - { url = "https://files.pythonhosted.org/packages/ce/7d/f0f59b3404caf662e7b5346247883887687c074ce67ba453ea08c612b1d5/coverage-7.13.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d2287ac9360dec3837bfdad969963a5d073a09a85d898bd86bea82aa8876ef3c", size = 263357, upload-time = "2025-12-28T15:41:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/1a/b1/29896492b0b1a047604d35d6fa804f12818fa30cdad660763a5f3159e158/coverage-7.13.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d2c11f3ea4db66b5cbded23b20185c35066892c67d80ec4be4bab257b9ad1e0", size = 260978, upload-time = "2025-12-28T15:41:54.589Z" }, - { url = "https://files.pythonhosted.org/packages/48/f2/971de1238a62e6f0a4128d37adadc8bb882ee96afbe03ff1570291754629/coverage-7.13.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:3fc6a169517ca0d7ca6846c3c5392ef2b9e38896f61d615cb75b9e7134d4ee1e", size = 259877, upload-time = "2025-12-28T15:41:56.263Z" }, - { url = "https://files.pythonhosted.org/packages/6a/fc/0474efcbb590ff8628830e9aaec5f1831594874360e3251f1fdec31d07a3/coverage-7.13.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d10a2ed46386e850bb3de503a54f9fe8192e5917fcbb143bfef653a9355e9a53", size = 262069, upload-time = "2025-12-28T15:41:58.093Z" }, - { url = "https://files.pythonhosted.org/packages/88/4f/3c159b7953db37a7b44c0eab8a95c37d1aa4257c47b4602c04022d5cb975/coverage-7.13.1-cp313-cp313t-win32.whl", hash = "sha256:75a6f4aa904301dab8022397a22c0039edc1f51e90b83dbd4464b8a38dc87842", size = 222184, upload-time = "2025-12-28T15:41:59.763Z" }, - { url = "https://files.pythonhosted.org/packages/58/a5/6b57d28f81417f9335774f20679d9d13b9a8fb90cd6160957aa3b54a2379/coverage-7.13.1-cp313-cp313t-win_amd64.whl", hash = "sha256:309ef5706e95e62578cda256b97f5e097916a2c26247c287bbe74794e7150df2", size = 223250, upload-time = "2025-12-28T15:42:01.52Z" }, - { url = "https://files.pythonhosted.org/packages/81/7c/160796f3b035acfbb58be80e02e484548595aa67e16a6345e7910ace0a38/coverage-7.13.1-cp313-cp313t-win_arm64.whl", hash = "sha256:92f980729e79b5d16d221038dbf2e8f9a9136afa072f9d5d6ed4cb984b126a09", size = 221521, upload-time = "2025-12-28T15:42:03.275Z" }, - { url = "https://files.pythonhosted.org/packages/aa/8e/ba0e597560c6563fc0adb902fda6526df5d4aa73bb10adf0574d03bd2206/coverage-7.13.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:97ab3647280d458a1f9adb85244e81587505a43c0c7cff851f5116cd2814b894", size = 218996, upload-time = "2025-12-28T15:42:04.978Z" }, - { url = "https://files.pythonhosted.org/packages/6b/8e/764c6e116f4221dc7aa26c4061181ff92edb9c799adae6433d18eeba7a14/coverage-7.13.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f572d989142e0908e6acf57ad1b9b86989ff057c006d13b76c146ec6a20216a", size = 219326, upload-time = "2025-12-28T15:42:06.691Z" }, - { url = "https://files.pythonhosted.org/packages/4f/a6/6130dc6d8da28cdcbb0f2bf8865aeca9b157622f7c0031e48c6cf9a0e591/coverage-7.13.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d72140ccf8a147e94274024ff6fd8fb7811354cf7ef88b1f0a988ebaa5bc774f", size = 250374, upload-time = "2025-12-28T15:42:08.786Z" }, - { url = "https://files.pythonhosted.org/packages/82/2b/783ded568f7cd6b677762f780ad338bf4b4750205860c17c25f7c708995e/coverage-7.13.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3c9f051b028810f5a87c88e5d6e9af3c0ff32ef62763bf15d29f740453ca909", size = 252882, upload-time = "2025-12-28T15:42:10.515Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b2/9808766d082e6a4d59eb0cc881a57fc1600eb2c5882813eefff8254f71b5/coverage-7.13.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f398ba4df52d30b1763f62eed9de5620dcde96e6f491f4c62686736b155aa6e4", size = 254218, upload-time = "2025-12-28T15:42:12.208Z" }, - { url = "https://files.pythonhosted.org/packages/44/ea/52a985bb447c871cb4d2e376e401116520991b597c85afdde1ea9ef54f2c/coverage-7.13.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:132718176cc723026d201e347f800cd1a9e4b62ccd3f82476950834dad501c75", size = 250391, upload-time = "2025-12-28T15:42:14.21Z" }, - { url = "https://files.pythonhosted.org/packages/7f/1d/125b36cc12310718873cfc8209ecfbc1008f14f4f5fa0662aa608e579353/coverage-7.13.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e549d642426e3579b3f4b92d0431543b012dcb6e825c91619d4e93b7363c3f9", size = 252239, upload-time = "2025-12-28T15:42:16.292Z" }, - { url = "https://files.pythonhosted.org/packages/6a/16/10c1c164950cade470107f9f14bbac8485f8fb8515f515fca53d337e4a7f/coverage-7.13.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:90480b2134999301eea795b3a9dbf606c6fbab1b489150c501da84a959442465", size = 250196, upload-time = "2025-12-28T15:42:18.54Z" }, - { url = "https://files.pythonhosted.org/packages/2a/c6/cd860fac08780c6fd659732f6ced1b40b79c35977c1356344e44d72ba6c4/coverage-7.13.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e825dbb7f84dfa24663dd75835e7257f8882629fc11f03ecf77d84a75134b864", size = 250008, upload-time = "2025-12-28T15:42:20.365Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/a8c58d3d38f82a5711e1e0a67268362af48e1a03df27c03072ac30feefcf/coverage-7.13.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:623dcc6d7a7ba450bbdbeedbaa0c42b329bdae16491af2282f12a7e809be7eb9", size = 251671, upload-time = "2025-12-28T15:42:22.114Z" }, - { url = "https://files.pythonhosted.org/packages/f0/bc/fd4c1da651d037a1e3d53e8cb3f8182f4b53271ffa9a95a2e211bacc0349/coverage-7.13.1-cp314-cp314-win32.whl", hash = "sha256:6e73ebb44dca5f708dc871fe0b90cf4cff1a13f9956f747cc87b535a840386f5", size = 221777, upload-time = "2025-12-28T15:42:23.919Z" }, - { url = "https://files.pythonhosted.org/packages/4b/50/71acabdc8948464c17e90b5ffd92358579bd0910732c2a1c9537d7536aa6/coverage-7.13.1-cp314-cp314-win_amd64.whl", hash = "sha256:be753b225d159feb397bd0bf91ae86f689bad0da09d3b301478cd39b878ab31a", size = 222592, upload-time = "2025-12-28T15:42:25.619Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c8/a6fb943081bb0cc926499c7907731a6dc9efc2cbdc76d738c0ab752f1a32/coverage-7.13.1-cp314-cp314-win_arm64.whl", hash = "sha256:228b90f613b25ba0019361e4ab81520b343b622fc657daf7e501c4ed6a2366c0", size = 221169, upload-time = "2025-12-28T15:42:27.629Z" }, - { url = "https://files.pythonhosted.org/packages/16/61/d5b7a0a0e0e40d62e59bc8c7aa1afbd86280d82728ba97f0673b746b78e2/coverage-7.13.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:60cfb538fe9ef86e5b2ab0ca8fc8d62524777f6c611dcaf76dc16fbe9b8e698a", size = 219730, upload-time = "2025-12-28T15:42:29.306Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2c/8881326445fd071bb49514d1ce97d18a46a980712b51fee84f9ab42845b4/coverage-7.13.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:57dfc8048c72ba48a8c45e188d811e5efd7e49b387effc8fb17e97936dde5bf6", size = 220001, upload-time = "2025-12-28T15:42:31.319Z" }, - { url = "https://files.pythonhosted.org/packages/b5/d7/50de63af51dfa3a7f91cc37ad8fcc1e244b734232fbc8b9ab0f3c834a5cd/coverage-7.13.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3f2f725aa3e909b3c5fdb8192490bdd8e1495e85906af74fe6e34a2a77ba0673", size = 261370, upload-time = "2025-12-28T15:42:32.992Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2c/d31722f0ec918fd7453b2758312729f645978d212b410cd0f7c2aed88a94/coverage-7.13.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ee68b21909686eeb21dfcba2c3b81fee70dcf38b140dcd5aa70680995fa3aa5", size = 263485, upload-time = "2025-12-28T15:42:34.759Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7a/2c114fa5c5fc08ba0777e4aec4c97e0b4a1afcb69c75f1f54cff78b073ab/coverage-7.13.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:724b1b270cb13ea2e6503476e34541a0b1f62280bc997eab443f87790202033d", size = 265890, upload-time = "2025-12-28T15:42:36.517Z" }, - { url = "https://files.pythonhosted.org/packages/65/d9/f0794aa1c74ceabc780fe17f6c338456bbc4e96bd950f2e969f48ac6fb20/coverage-7.13.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:916abf1ac5cf7eb16bc540a5bf75c71c43a676f5c52fcb9fe75a2bd75fb944e8", size = 260445, upload-time = "2025-12-28T15:42:38.646Z" }, - { url = "https://files.pythonhosted.org/packages/49/23/184b22a00d9bb97488863ced9454068c79e413cb23f472da6cbddc6cfc52/coverage-7.13.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:776483fd35b58d8afe3acbd9988d5de592ab6da2d2a865edfdbc9fdb43e7c486", size = 263357, upload-time = "2025-12-28T15:42:40.788Z" }, - { url = "https://files.pythonhosted.org/packages/7d/bd/58af54c0c9199ea4190284f389005779d7daf7bf3ce40dcd2d2b2f96da69/coverage-7.13.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b6f3b96617e9852703f5b633ea01315ca45c77e879584f283c44127f0f1ec564", size = 260959, upload-time = "2025-12-28T15:42:42.808Z" }, - { url = "https://files.pythonhosted.org/packages/4b/2a/6839294e8f78a4891bf1df79d69c536880ba2f970d0ff09e7513d6e352e9/coverage-7.13.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:bd63e7b74661fed317212fab774e2a648bc4bb09b35f25474f8e3325d2945cd7", size = 259792, upload-time = "2025-12-28T15:42:44.818Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c3/528674d4623283310ad676c5af7414b9850ab6d55c2300e8aa4b945ec554/coverage-7.13.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:933082f161bbb3e9f90d00990dc956120f608cdbcaeea15c4d897f56ef4fe416", size = 262123, upload-time = "2025-12-28T15:42:47.108Z" }, - { url = "https://files.pythonhosted.org/packages/06/c5/8c0515692fb4c73ac379d8dc09b18eaf0214ecb76ea6e62467ba7a1556ff/coverage-7.13.1-cp314-cp314t-win32.whl", hash = "sha256:18be793c4c87de2965e1c0f060f03d9e5aff66cfeae8e1dbe6e5b88056ec153f", size = 222562, upload-time = "2025-12-28T15:42:49.144Z" }, - { url = "https://files.pythonhosted.org/packages/05/0e/c0a0c4678cb30dac735811db529b321d7e1c9120b79bd728d4f4d6b010e9/coverage-7.13.1-cp314-cp314t-win_amd64.whl", hash = "sha256:0e42e0ec0cd3e0d851cb3c91f770c9301f48647cb2877cb78f74bdaa07639a79", size = 223670, upload-time = "2025-12-28T15:42:51.218Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5f/b177aa0011f354abf03a8f30a85032686d290fdeed4222b27d36b4372a50/coverage-7.13.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eaecf47ef10c72ece9a2a92118257da87e460e113b83cc0d2905cbbe931792b4", size = 221707, upload-time = "2025-12-28T15:42:53.034Z" }, - { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, -] - -[package.optional-dependencies] -toml = [ - { name = "tomli", marker = "python_full_version <= '3.11'" }, -] - [[package]] name = "crispy-bootstrap4" version = "2024.10" @@ -1200,15 +1007,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178, upload-time = "2020-04-20T14:23:36.581Z" }, ] -[[package]] -name = "distlib" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, -] - [[package]] name = "django" version = "5.2.9" @@ -1449,15 +1247,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, ] -[[package]] -name = "execnet" -version = "2.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, -] - [[package]] name = "fastmcp" version = "3.2.0" @@ -1529,24 +1318,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/10/8be05dd87b7080e1f742446904bc5f13b31943645c85537b5b04a6a5ecd5/flare_capa-9.3.1-py3-none-any.whl", hash = "sha256:caf59bfeb5e2b5d4ab9111e79cce153b585d0839419f36b9e63141bad9f92424", size = 1202628, upload-time = "2025-11-20T17:03:18.594Z" }, ] -[[package]] -name = "freezegun" -version = "1.5.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-dateutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/95/dd/23e2f4e357f8fd3bdff613c1fe4466d21bfb00a6177f238079b17f7b1c84/freezegun-1.5.5.tar.gz", hash = "sha256:ac7742a6cc6c25a2c35e9292dfd554b897b517d2dec26891a2e8debf205cb94a", size = 35914, upload-time = "2025-08-09T10:39:08.338Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/2e/b41d8a1a917d6581fc27a35d05561037b048e47df50f27f8ac9c7e27a710/freezegun-1.5.5-py3-none-any.whl", hash = "sha256:cd557f4a75cf074e84bc374249b9dd491eaeacd61376b9eb3c423282211619d2", size = 19266, upload-time = "2025-08-09T10:39:06.636Z" }, -] - -[[package]] -name = "func-timeout" -version = "4.3.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/0d/bf0567477f7281d9a3926c582bfef21bff7498fc0ffd3e9de21811896a0b/func_timeout-4.3.5.tar.gz", hash = "sha256:74cd3c428ec94f4edfba81f9b2f14904846d5ffccc27c92433b8b5939b5575dd", size = 44264, upload-time = "2019-08-19T21:32:07.43Z" } - [[package]] name = "funcy" version = "2.0" @@ -1950,12 +1721,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] -[[package]] -name = "httpretty" -version = "1.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6e/19/850b7ed736319d0c4088581f4fc34f707ef14461947284026664641e16d4/httpretty-1.1.4.tar.gz", hash = "sha256:20de0e5dd5a18292d36d928cc3d6e52f8b2ac73daec40d41eb62dee154933b68", size = 442389, upload-time = "2021-08-16T19:35:31.4Z" } - [[package]] name = "httptools" version = "0.7.1" @@ -2118,15 +1883,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/5d/6e6565d9a1d6ecf1b48a2bea326f0e3dbe4c3199adfc0a5456b83d235f99/idapro-0.0.7-py3-none-any.whl", hash = "sha256:3cb8d48ac21a19e6a0ef65eacd0005a1f9c40a264a7bcb675914eaa20c6e1371", size = 1999440, upload-time = "2025-10-29T09:07:19.998Z" }, ] -[[package]] -name = "identify" -version = "2.6.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, -] - [[package]] name = "idna" version = "3.11" @@ -2161,15 +1917,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/55/0f4df2a44053867ea9cbea73fc588b03c55605cd695cee0a3d86f0029cb2/incremental-24.11.0-py3-none-any.whl", hash = "sha256:a34450716b1c4341fe6676a0598e88a39e04189f4dce5dc96f656e040baa10b3", size = 21109, upload-time = "2025-11-28T02:30:16.442Z" }, ] -[[package]] -name = "iniconfig" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, -] - [[package]] name = "iniparse" version = "0.5" @@ -2194,15 +1941,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/7f/8a80a1c7c2ed05822b5a2b312d2995f30c533641f8198366ba2e26a7bb03/intervaltree-3.2.1-py2.py3-none-any.whl", hash = "sha256:a8a8381bbd35d48ceebee932c77ffc988492d22fb1d27d0ba1d74a7694eb8f0b", size = 25929, upload-time = "2025-12-24T04:25:05.298Z" }, ] -[[package]] -name = "isort" -version = "7.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/63/53/4f3c058e3bace40282876f9b553343376ee687f3c35a525dc79dbd450f88/isort-7.0.0.tar.gz", hash = "sha256:5513527951aadb3ac4292a41a16cbc50dd1642432f5e8c20057d414bdafb4187", size = 805049, upload-time = "2025-10-11T13:30:59.107Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/ed/e3705d6d02b4f7aea715a353c8ce193efd0b5db13e204df895d38734c244/isort-7.0.0-py3-none-any.whl", hash = "sha256:1bcabac8bc3c36c7fb7b98a76c8abb18e0f841a3ba81decac7691008592499c1", size = 94672, upload-time = "2025-10-11T13:30:57.665Z" }, -] - [[package]] name = "jaraco-classes" version = "3.4.0" @@ -2946,53 +2684,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, ] -[[package]] -name = "mypy" -version = "1.14.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/eb/2c92d8ea1e684440f54fa49ac5d9a5f19967b7b472a281f419e69a8d228e/mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6", size = 3216051, upload-time = "2024-12-30T16:39:07.335Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/7a/87ae2adb31d68402da6da1e5f30c07ea6063e9f09b5e7cfc9dfa44075e74/mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb", size = 11211002, upload-time = "2024-12-30T16:37:22.435Z" }, - { url = "https://files.pythonhosted.org/packages/e1/23/eada4c38608b444618a132be0d199b280049ded278b24cbb9d3fc59658e4/mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0", size = 10358400, upload-time = "2024-12-30T16:37:53.526Z" }, - { url = "https://files.pythonhosted.org/packages/43/c9/d6785c6f66241c62fd2992b05057f404237deaad1566545e9f144ced07f5/mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d", size = 12095172, upload-time = "2024-12-30T16:37:50.332Z" }, - { url = "https://files.pythonhosted.org/packages/c3/62/daa7e787770c83c52ce2aaf1a111eae5893de9e004743f51bfcad9e487ec/mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b", size = 12828732, upload-time = "2024-12-30T16:37:29.96Z" }, - { url = "https://files.pythonhosted.org/packages/1b/a2/5fb18318a3637f29f16f4e41340b795da14f4751ef4f51c99ff39ab62e52/mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427", size = 13012197, upload-time = "2024-12-30T16:38:05.037Z" }, - { url = "https://files.pythonhosted.org/packages/28/99/e153ce39105d164b5f02c06c35c7ba958aaff50a2babba7d080988b03fe7/mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f", size = 9780836, upload-time = "2024-12-30T16:37:19.726Z" }, - { url = "https://files.pythonhosted.org/packages/da/11/a9422850fd506edbcdc7f6090682ecceaf1f87b9dd847f9df79942da8506/mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c", size = 11120432, upload-time = "2024-12-30T16:37:11.533Z" }, - { url = "https://files.pythonhosted.org/packages/b6/9e/47e450fd39078d9c02d620545b2cb37993a8a8bdf7db3652ace2f80521ca/mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1", size = 10279515, upload-time = "2024-12-30T16:37:40.724Z" }, - { url = "https://files.pythonhosted.org/packages/01/b5/6c8d33bd0f851a7692a8bfe4ee75eb82b6983a3cf39e5e32a5d2a723f0c1/mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8", size = 12025791, upload-time = "2024-12-30T16:36:58.73Z" }, - { url = "https://files.pythonhosted.org/packages/f0/4c/e10e2c46ea37cab5c471d0ddaaa9a434dc1d28650078ac1b56c2d7b9b2e4/mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f", size = 12749203, upload-time = "2024-12-30T16:37:03.741Z" }, - { url = "https://files.pythonhosted.org/packages/88/55/beacb0c69beab2153a0f57671ec07861d27d735a0faff135a494cd4f5020/mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1", size = 12885900, upload-time = "2024-12-30T16:37:57.948Z" }, - { url = "https://files.pythonhosted.org/packages/a2/75/8c93ff7f315c4d086a2dfcde02f713004357d70a163eddb6c56a6a5eff40/mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae", size = 9777869, upload-time = "2024-12-30T16:37:33.428Z" }, - { url = "https://files.pythonhosted.org/packages/43/1b/b38c079609bb4627905b74fc6a49849835acf68547ac33d8ceb707de5f52/mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14", size = 11266668, upload-time = "2024-12-30T16:38:02.211Z" }, - { url = "https://files.pythonhosted.org/packages/6b/75/2ed0d2964c1ffc9971c729f7a544e9cd34b2cdabbe2d11afd148d7838aa2/mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9", size = 10254060, upload-time = "2024-12-30T16:37:46.131Z" }, - { url = "https://files.pythonhosted.org/packages/a1/5f/7b8051552d4da3c51bbe8fcafffd76a6823779101a2b198d80886cd8f08e/mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11", size = 11933167, upload-time = "2024-12-30T16:37:43.534Z" }, - { url = "https://files.pythonhosted.org/packages/04/90/f53971d3ac39d8b68bbaab9a4c6c58c8caa4d5fd3d587d16f5927eeeabe1/mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e", size = 12864341, upload-time = "2024-12-30T16:37:36.249Z" }, - { url = "https://files.pythonhosted.org/packages/03/d2/8bc0aeaaf2e88c977db41583559319f1821c069e943ada2701e86d0430b7/mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89", size = 12972991, upload-time = "2024-12-30T16:37:06.743Z" }, - { url = "https://files.pythonhosted.org/packages/6f/17/07815114b903b49b0f2cf7499f1c130e5aa459411596668267535fe9243c/mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b", size = 9879016, upload-time = "2024-12-30T16:37:15.02Z" }, - { url = "https://files.pythonhosted.org/packages/9e/15/bb6a686901f59222275ab228453de741185f9d54fecbaacec041679496c6/mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255", size = 11252097, upload-time = "2024-12-30T16:37:25.144Z" }, - { url = "https://files.pythonhosted.org/packages/f8/b3/8b0f74dfd072c802b7fa368829defdf3ee1566ba74c32a2cb2403f68024c/mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34", size = 10239728, upload-time = "2024-12-30T16:38:08.634Z" }, - { url = "https://files.pythonhosted.org/packages/c5/9b/4fd95ab20c52bb5b8c03cc49169be5905d931de17edfe4d9d2986800b52e/mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a", size = 11924965, upload-time = "2024-12-30T16:38:12.132Z" }, - { url = "https://files.pythonhosted.org/packages/56/9d/4a236b9c57f5d8f08ed346914b3f091a62dd7e19336b2b2a0d85485f82ff/mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9", size = 12867660, upload-time = "2024-12-30T16:38:17.342Z" }, - { url = "https://files.pythonhosted.org/packages/40/88/a61a5497e2f68d9027de2bb139c7bb9abaeb1be1584649fa9d807f80a338/mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd", size = 12969198, upload-time = "2024-12-30T16:38:32.839Z" }, - { url = "https://files.pythonhosted.org/packages/54/da/3d6fc5d92d324701b0c23fb413c853892bfe0e1dbe06c9138037d459756b/mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107", size = 9885276, upload-time = "2024-12-30T16:38:20.828Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b5/32dd67b69a16d088e533962e5044e51004176a9952419de0370cdaead0f8/mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1", size = 2752905, upload-time = "2024-12-30T16:38:42.021Z" }, -] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, -] - [[package]] name = "netstruct" version = "1.1.2" @@ -3031,15 +2722,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, ] -[[package]] -name = "nodeenv" -version = "1.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, -] - [[package]] name = "olefile" version = "0.47" @@ -3231,15 +2913,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/96/5a770e5c461462575474468e5af931cff9de036e7c2b4fea23c1c58d2cbe/pathable-0.5.0-py3-none-any.whl", hash = "sha256:646e3d09491a6351a0c82632a09c02cdf70a252e73196b36d8a15ba0a114f0a6", size = 16867, upload-time = "2026-02-20T08:46:59.536Z" }, ] -[[package]] -name = "pathspec" -version = "1.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, -] - [[package]] name = "pcodedmp" version = "1.2.6" @@ -3408,12 +3081,12 @@ wheels = [ ] [[package]] -name = "pluggy" -version = "1.6.0" +name = "plyara" +version = "2.2.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/4b/0441fb4595df522053c1899ed3280d0bb3b8bc3fa1de415b87ec3537ec62/plyara-2.2.8.tar.gz", hash = "sha256:ce6a5be6bdc172f78bb10d2e220409c76bd46b3da9ff43e53bbe84d1eede9700", size = 68720, upload-time = "2025-02-06T21:06:24.093Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/c3/62/10ecb9f3d6a5e620e3ae496a048838697a7113ccace02cb360f24015b065/plyara-2.2.8-py3-none-any.whl", hash = "sha256:8b2a3b46c0963976010a69d3ddd280a77dcd6cf1097408ff573b5af5df04d06d", size = 55187, upload-time = "2025-02-06T21:06:21.311Z" }, ] [[package]] @@ -3432,22 +3105,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ed/b6/b20f69577f693d415981af21e2b3b03308ef50c79a2ee7a8bed796791965/postgrest-2.25.1-py3-none-any.whl", hash = "sha256:8fb7944c613022398ff1e643621c232b170d363a7333b9dd316360ab37dc5b4e", size = 21582, upload-time = "2025-12-10T21:48:27.017Z" }, ] -[[package]] -name = "pre-commit" -version = "4.5.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cfgv" }, - { name = "identify" }, - { name = "nodeenv" }, - { name = "pyyaml" }, - { name = "virtualenv" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, -] - [[package]] name = "prettytable" version = "3.17.0" @@ -4224,113 +3881,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8d/59/b4572118e098ac8e46e399a1dd0f2d85403ce8bbaad9ec79373ed6badaf9/PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5", size = 16725, upload-time = "2019-09-20T02:06:22.938Z" }, ] -[[package]] -name = "pytest" -version = "7.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/29/311895d9cd3f003dd58e8fdea36dd895ba2da5c0c90601836f7de79f76fe/pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4", size = 1320028, upload-time = "2023-03-03T19:12:29.775Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/68/5321b5793bd506961bd40bdbdd0674e7de4fb873ee7cab33dd27283ad513/pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e", size = 317207, upload-time = "2023-03-03T19:12:27.611Z" }, -] - -[[package]] -name = "pytest-asyncio" -version = "0.18.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4d/73/769d29676fb36a36e5a57c198154171081aabcfd08112a24a4e3fb5c9f10/pytest-asyncio-0.18.3.tar.gz", hash = "sha256:7659bdb0a9eb9c6e3ef992eef11a2b3e69697800ad02fb06374a210d85b29f91", size = 28052, upload-time = "2022-03-25T09:43:58.406Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/d6/4ecdd0c5b49a2209131b6af78baa643cec35f213abbc54d0eb1542b3786d/pytest_asyncio-0.18.3-1-py3-none-any.whl", hash = "sha256:16cf40bdf2b4fb7fc8e4b82bd05ce3fbcd454cbf7b92afc445fe299dabb88213", size = 14768, upload-time = "2022-03-28T13:53:15.727Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4b/7c400506ec484ec999b10133aa8e31af39dfc727042dc6944cd45fd927d0/pytest_asyncio-0.18.3-py3-none-any.whl", hash = "sha256:8fafa6c52161addfd41ee7ab35f11836c5a16ec208f93ee388f752bea3493a84", size = 14597, upload-time = "2022-03-25T09:43:57.106Z" }, -] - -[[package]] -name = "pytest-cov" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coverage", extra = ["toml"] }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/61/41/e046526849972555928a6d31c2068410e47a31fb5ab0a77f868596811329/pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470", size = 61440, upload-time = "2021-10-04T01:48:59.602Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/49/b3e0edec68d81846f519c602ac38af9db86e1e71275528b3e814ae236063/pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6", size = 20981, upload-time = "2021-10-04T01:48:57.552Z" }, -] - -[[package]] -name = "pytest-django" -version = "4.5.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9b/42/6d6563165b82289d4a30ea477f85c04386303e51cf4e4e4651d4f9910830/pytest-django-4.5.2.tar.gz", hash = "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2", size = 79949, upload-time = "2021-12-07T14:26:58.991Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/21/b65ecd6686da400e2f6e3c49c2a428325abd979c9670cd97e1671f53296e/pytest_django-4.5.2-py3-none-any.whl", hash = "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e", size = 20752, upload-time = "2021-12-07T14:26:57.714Z" }, -] - -[[package]] -name = "pytest-freezer" -version = "0.4.8" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "freezegun" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/fa/a93d40dd50f712c276a5a15f9c075bee932cc4d28c376e60b4a35904976d/pytest_freezer-0.4.8.tar.gz", hash = "sha256:8ee2f724b3ff3540523fa355958a22e6f4c1c819928b78a7a183ae4248ce6ee6", size = 3212, upload-time = "2023-06-21T05:31:25.753Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/4e/ba488639516a341810aeaeb4b32b70abb0923e53f7c4d14d673dc114d35a/pytest_freezer-0.4.8-py3-none-any.whl", hash = "sha256:644ce7ddb8ba52b92a1df0a80a699bad2b93514c55cf92e9f2517b68ebe74814", size = 3228, upload-time = "2023-06-21T05:31:24.283Z" }, -] - -[[package]] -name = "pytest-mock" -version = "3.7.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/96/e1/fb53b62056e6840a36d9a4beb4e42726155594c567b574103435a7131c60/pytest-mock-3.7.0.tar.gz", hash = "sha256:5112bd92cc9f186ee96e1a92efc84969ea494939c3aead39c50f421c4cc69534", size = 29311, upload-time = "2022-01-28T11:26:00.338Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/40/8fcb3c0f72e11dc44e1102b2adf5f160b8a00e84d915798c60aabcd9257a/pytest_mock-3.7.0-py3-none-any.whl", hash = "sha256:6cff27cec936bf81dc5ee87f07132b807bcda51106b5ec4b90a04331cba76231", size = 12658, upload-time = "2022-01-28T11:25:58.522Z" }, -] - -[[package]] -name = "pytest-pretty" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, - { name = "rich" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/05/34/83ca87833c2525280e05fb5aa86fe8dc1db8c473fa4387d6db6c3511796a/pytest_pretty-1.1.0.tar.gz", hash = "sha256:425e116c1ed10ce67eeb688f22d1bcb91ca58a97986026bf42c0717da80740b3", size = 6469, upload-time = "2023-02-01T16:18:06.586Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/39/ff6ca84f7701848e288343c8e7a931babbb2908ff7c364e41d761da647ab/pytest_pretty-1.1.0-py3-none-any.whl", hash = "sha256:e7e63e0437694e5a7c557d99c81d6e29e0dcaab3b6b9dcb3ba23ddf3eebb5b42", size = 6107, upload-time = "2023-02-01T16:18:05.448Z" }, -] - -[[package]] -name = "pytest-xdist" -version = "3.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "execnet" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/41/c4/3c310a19bc1f1e9ef50075582652673ef2bfc8cd62afef9585683821902f/pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d", size = 84060, upload-time = "2024-04-28T19:29:54.414Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108, upload-time = "2024-04-28T19:29:52.813Z" }, -] - [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -4450,45 +4000,6 @@ version = "1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/4d/5b/eddaf843bf7859fda566e3b852c87d149e15543ae2bb6f710a44e8c1baf9/pythonaes-1.0.tar.gz", hash = "sha256:71dd31c03500b8cba06f83f17603dcca1dd1c1308fbdaef752f353ed1aaf9f67", size = 10158, upload-time = "2016-06-30T21:18:23.465Z" } -[[package]] -name = "pytokens" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" }, - { url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" }, - { url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" }, - { url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" }, - { url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" }, - { url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" }, - { url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" }, - { url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" }, - { url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" }, - { url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" }, - { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, - { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, - { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, - { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, - { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, - { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, - { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, - { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, - { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, - { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, - { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, - { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, - { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, - { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, - { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, - { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, - { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, -] - [[package]] name = "pytz" version = "2021.1" @@ -5346,18 +4857,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/50/05/bdb6318120cac9bf97779674f49035e0595d894b42d4c43b60637bafdb1f/txaio-25.12.2-py3-none-any.whl", hash = "sha256:5f6cd6c6b397fc3305790d15efd46a2d5b91cdbefa96543b4f8666aeb56ba026", size = 31208, upload-time = "2025-12-09T04:30:27.811Z" }, ] -[[package]] -name = "types-requests" -version = "2.32.4.20250913" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d", size = 23113, upload-time = "2025-09-13T02:40:02.309Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658, upload-time = "2025-09-13T02:40:01.115Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" @@ -5587,21 +5086,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, ] -[[package]] -name = "virtualenv" -version = "20.36.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "distlib" }, - { name = "filelock" }, - { name = "platformdirs" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" }, -] - [[package]] name = "viv-utils" version = "0.8.1" From 855500fe9401b59db2614efd7441a9c4e5e7ce43 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Fri, 15 May 2026 08:49:24 -0400 Subject: [PATCH 05/29] Update installer/cape2.sh Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- installer/cape2.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer/cape2.sh b/installer/cape2.sh index 9371d75a0ec..3467db1077c 100755 --- a/installer/cape2.sh +++ b/installer/cape2.sh @@ -1605,7 +1605,7 @@ function install_guacamole() { sudo usermod www-data -G ${USER} cd $CAPE_ROOT - sudo -u ${USER} bash -c "cd $CAPE_ROOT && export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring; ${poetry_path} $PYTHON_MGR_INSTALL_PYPROJECT" + sudo -u ${USER} bash -c "cd $CAPE_ROOT && export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring; $PYTHON_MGR $PYTHON_MGR_INSTALL_PYPROJECT" cd .. systemctl daemon-reload From a3e1b11afe60a2476265681055d1ff21458eaceb Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Fri, 15 May 2026 08:49:50 -0400 Subject: [PATCH 06/29] Update installer/cape2.sh Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- installer/cape2.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/installer/cape2.sh b/installer/cape2.sh index 3467db1077c..2c80cf450af 100755 --- a/installer/cape2.sh +++ b/installer/cape2.sh @@ -1355,7 +1355,7 @@ function install_CAPE() { echo "[-] pyproject.toml not found in $CAPE_ROOT" return fi - sudo -u ${USER} bash -c "cd $CAPE_ROOT && export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring; $PYTHON_MGR $PYTHON_MGR_INSTALL_PYPROJECT" + sudo -u ${USER} bash -c "cd $CAPE_ROOT && export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring; export CRYPTOGRAPHY_DONT_BUILD_RUST=1; $PYTHON_MGR $PYTHON_MGR_INSTALL_PYPROJECT" if [ "$DISABLE_LIBVIRT" -eq 0 ]; then # Integrated libvirt install From e6fcc1cb340eaad715ad2a2d597d7e8f576c3f53 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Fri, 15 May 2026 08:50:01 -0400 Subject: [PATCH 07/29] Update installer/cape2.sh Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- installer/cape2.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/installer/cape2.sh b/installer/cape2.sh index 2c80cf450af..6135636aa14 100755 --- a/installer/cape2.sh +++ b/installer/cape2.sh @@ -1441,8 +1441,8 @@ function install_systemd() { if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then # Remove poetry config ExecStartPre lines BEFORE replacing poetry→uv so the # pattern still matches (after replacement the path no longer contains /poetry) - sed -i "s|^ExecStartPre=.*/poetry .*||g" /lib/systemd/system/cape-fstab.service || true - sed -i "s|^ExecStartPre=.*/poetry .*||g" /lib/systemd/system/cape-rooter.service || true + sed -i "\|^ExecStartPre=.*/poetry .*|d" /lib/systemd/system/cape-fstab.service || true + sed -i "\|^ExecStartPre=.*/poetry .*|d" /lib/systemd/system/cape-rooter.service || true sed -i "s|/etc/poetry/bin/poetry|$PYTHON_MGR|g" /lib/systemd/system/cape*.service sed -i "s|/etc/poetry/bin/poetry|$PYTHON_MGR|g" /lib/systemd/system/guac*.service fi From 70d5bfe593480e116d44720ee230abbde6b5d4b5 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Fri, 15 May 2026 08:52:45 -0400 Subject: [PATCH 08/29] Manually apply gemini suggestion --- installer/cape2.sh | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/installer/cape2.sh b/installer/cape2.sh index 6135636aa14..3540f143819 100755 --- a/installer/cape2.sh +++ b/installer/cape2.sh @@ -1511,13 +1511,8 @@ function install_node_exporter() { function install_volatility3() { echo "[+] Installing volatility3" sudo apt-get install -y unzip - if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then - sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR $PYTHON_MGR_PIP install git+https://github.com/volatilityfoundation/volatility3" - vol_path=$(sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR $PYTHON_MGR_CMD python3 -c \"import volatility3.plugins;print(volatility3.__file__.replace('__init__.py', 'symbols/'))\"") - else - sudo -u ${USER} $PYTHON_MGR $PYTHON_MGR_PIP install git+https://github.com/volatilityfoundation/volatility3 - vol_path=$(sudo -u ${USER} $PYTHON_MGR $PYTHON_MGR_CMD python3 -c "import volatility3.plugins;print(volatility3.__file__.replace('__init__.py', 'symbols/'))") - fi + sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR $PYTHON_MGR_PIP install git+https://github.com/volatilityfoundation/volatility3" + vol_path=$(sudo -u ${USER} bash -c "cd $CAPE_ROOT && $PYTHON_MGR $PYTHON_MGR_CMD python3 -c \"import volatility3.plugins;print(volatility3.__file__.replace('__init__.py', 'symbols/'))\"") if [ -z "$vol_path" ]; then echo "[-] Could not find volatility3 path" From a10acad85eadd39ddbb1dc654767ecb920a2fbfe Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Fri, 15 May 2026 09:30:47 -0400 Subject: [PATCH 09/29] Fix suricata install ordering and missing groups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit install_suricata must run before install_systemd so suricata is present when systemctl first starts the service. Also create pcap and suricata groups if absent — the OISF PPA package does not create them. --- installer/cape2.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/installer/cape2.sh b/installer/cape2.sh index 3540f143819..c65432ecfbe 100755 --- a/installer/cape2.sh +++ b/installer/cape2.sh @@ -760,6 +760,8 @@ file-store.enabled: yes EOF sed -i '$a include:\n - cape.yaml\n' /etc/suricata/suricata.yaml + getent group pcap || groupadd --system pcap + getent group suricata || groupadd --system suricata usermod -aG pcap suricata usermod -aG suricata "${USER}" # sudo chmod -R g+w /var/log/suricata/ @@ -1753,8 +1755,8 @@ case "$COMMAND" in install_mongo install_CAPE install_yara - install_systemd install_suricata + install_systemd install_jemalloc if ! crontab -l | grep -q './smtp_sinkhole.sh'; then crontab -l | { cat; echo "@reboot cd $CAPE_ROOT/utils/ && ./smtp_sinkhole.sh 2>/dev/null"; } | crontab - @@ -1776,8 +1778,8 @@ case "$COMMAND" in install_volatility3 install_mongo install_yara - install_systemd install_suricata + install_systemd install_jemalloc install_logrotate install_mitmproxy From ebd55e5cc6265a65e14fba343ebb94e45a454fbc Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Fri, 15 May 2026 09:33:57 -0400 Subject: [PATCH 10/29] Fix uv install: create .venv in install_dependencies after user setup --- installer/cape2.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/installer/cape2.sh b/installer/cape2.sh index c65432ecfbe..cf7c86a9029 100755 --- a/installer/cape2.sh +++ b/installer/cape2.sh @@ -1118,6 +1118,10 @@ function dependencies() { useradd --system -g ${USER} -d /home/${USER}/ -m ${USER} --shell /bin/bash fi + if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then + sudo -u ${USER} /usr/local/bin/uv venv "$CAPE_ROOT/.venv" + fi + echo "${USER} ALL=NOPASSWD: ${TCPDUMP_PATH}" > /etc/sudoers.d/tcpdump chmod 440 /etc/sudoers.d/tcpdump From 1789adf141920fd09e13af8f9c33bda61c6a43be Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Fri, 15 May 2026 15:55:35 -0400 Subject: [PATCH 11/29] Fix IFACE_IP arg parsing: accept IP as $2 with any number of additional flags Previously IFACE_IP was only set when $# -eq 3, making it impossible to pass both an interface IP and flags like --use-uv simultaneously. Change to -ge 2 and read IFACE_IP from $2 directly (sandbox_version was set but never used). --- installer/cape2.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/installer/cape2.sh b/installer/cape2.sh index cf7c86a9029..3405e6ed326 100755 --- a/installer/cape2.sh +++ b/installer/cape2.sh @@ -1717,9 +1717,8 @@ case $COMMAND in exit 0;; esac -if [ $# -eq 3 ]; then - sandbox_version=$2 - IFACE_IP=$3 +if [ $# -ge 2 ]; then + IFACE_IP=$2 elif [ $# -eq 0 ]; then echo "[-] check --help" exit 1 From b7357758126a3841f2095178ecea1625b2da4485 Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Fri, 15 May 2026 15:58:42 -0400 Subject: [PATCH 12/29] Fix uv venv creation: ensure CAPE_ROOT exists before creating venv dependencies() runs before install_CAPE() which is where CAPE_ROOT is created/cloned. On a fresh install, the uv venv call would fail because the directory doesn't exist yet. --- installer/cape2.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/installer/cape2.sh b/installer/cape2.sh index 3405e6ed326..e5c4c88040b 100755 --- a/installer/cape2.sh +++ b/installer/cape2.sh @@ -1119,6 +1119,8 @@ function dependencies() { fi if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then + mkdir -p "$CAPE_ROOT" + chown ${USER}:${USER} "$CAPE_ROOT" sudo -u ${USER} /usr/local/bin/uv venv "$CAPE_ROOT/.venv" fi From e177d0d20aef8a398140ba4821e051183d92563c Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Fri, 15 May 2026 16:00:25 -0400 Subject: [PATCH 13/29] Fix USE_UV env var not selecting uv as package manager PYTHON_MGR vars were only set to uv inside the CLI arg-parsing loop, so USE_UV=True in the environment (or cape-config.sh) would install uv but still run poetry commands. Extract assignment into set_python_mgr() and call it after sourcing cape-config.sh so all three entry points (env var, config file, --use-uv flag) work correctly. --- installer/cape2.sh | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/installer/cape2.sh b/installer/cape2.sh index e5c4c88040b..eb54908402b 100755 --- a/installer/cape2.sh +++ b/installer/cape2.sh @@ -64,16 +64,28 @@ TOR_SOCKET_TIMEOUT="60" CAPE_ROOT="${CAPE_ROOT:-/opt/CAPEv2}" USE_UV=${USE_UV:-false} -PYTHON_MGR="/etc/poetry/bin/poetry" -PYTHON_MGR_CMD="run" -PYTHON_MGR_PIP="run pip" -PYTHON_MGR_INSTALL_PYPROJECT="install" + +set_python_mgr() { + if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then + PYTHON_MGR="/usr/local/bin/uv" + PYTHON_MGR_CMD="run" + PYTHON_MGR_PIP="pip" + PYTHON_MGR_INSTALL_PYPROJECT="sync --no-install-project" + else + PYTHON_MGR="/etc/poetry/bin/poetry" + PYTHON_MGR_CMD="run" + PYTHON_MGR_PIP="run pip" + PYTHON_MGR_INSTALL_PYPROJECT="install" + fi +} # if a config file is present, read it in if [ -f "./cape-config.sh" ]; then . ./cape-config.sh fi +set_python_mgr + UBUNTU_VERSION=$(lsb_release -rs) OS="$(uname -s)" MAINTAINER="$(whoami) "_"$(hostname)" @@ -1739,10 +1751,7 @@ for i in "$@"; do DISABLE_LIBVIRT=1 elif [ "$i" == "--use-uv" ] || [ "$i" == "USE_UV=true" ] || [ "$i" == "USE_UV=True" ]; then USE_UV="true" - PYTHON_MGR="/usr/local/bin/uv" - PYTHON_MGR_CMD="run" - PYTHON_MGR_PIP="pip" - PYTHON_MGR_INSTALL_PYPROJECT="sync --no-install-project" + set_python_mgr fi done From 5a89aa778a2af6dcc22d4527329b602f0592d72a Mon Sep 17 00:00:00 2001 From: "Edward J. Schwartz" Date: Sat, 16 May 2026 09:18:13 -0400 Subject: [PATCH 14/29] Fix LIBVIRT_DEFAULT_URI set only when installing virt-manager Extract shell-profile export into idempotent _set_libvirt_default_uri helper and call it from install_libvirt so the variable is set on server installs too. Also fixes ~/.zsh typo (should be ~/.zshrc). --- installer/kvm-qemu.sh | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/installer/kvm-qemu.sh b/installer/kvm-qemu.sh index 2e38e6bf095..84b9a3b0743 100755 --- a/installer/kvm-qemu.sh +++ b/installer/kvm-qemu.sh @@ -601,6 +601,18 @@ EOH echo "[+] You should logout and login " fi + _set_libvirt_default_uri +} + +function _set_libvirt_default_uri() { + local rc_file + if [ "$SHELL" = "/bin/zsh" ] || [ "$SHELL" = "/usr/bin/zsh" ]; then + rc_file="$HOME/.zshrc" + else + rc_file="$HOME/.bashrc" + fi + grep -qxF 'export LIBVIRT_DEFAULT_URI=qemu:///system' "$rc_file" 2>/dev/null \ + || echo 'export LIBVIRT_DEFAULT_URI=qemu:///system' >> "$rc_file" } function install_virt_manager() { @@ -681,13 +693,7 @@ function install_virt_manager() { # https://github.com/virt-manager/virt-manager/blob/main/INSTALL.md meson setup build meson install -C build - if [ "$SHELL" = "/bin/zsh" ] || [ "$SHELL" = "/usr/bin/zsh" ] ; then - echo "export LIBVIRT_DEFAULT_URI=qemu:///system" >> "$HOME/.zsh" - # echo "export GI_TYPELIB_PATH=/usr/local/lib/girepository-1.0:$GI_TYPELIB_PATH" >> "$HOME/.zsh" - else - echo "export LIBVIRT_DEFAULT_URI=qemu:///system" >> "$HOME/.bashrc" - # echo "export GI_TYPELIB_PATH=/usr/local/lib/girepository-1.0:$GI_TYPELIB_PATH" >> "$HOME/.bashrc" - fi + _set_libvirt_default_uri if [ -f /usr/share/virt-manager/local/share/glib-2.0/schemas/org.virt-manager.virt-manager.gschema.xml ]; then cp /usr/share/virt-manager/local/share/glib-2.0/schemas/org.virt-manager.virt-manager.gschema.xml /usr/share/glib-2.0/schemas/ From 273877c348a6c74743c673309a2cd4ff50fc5263 Mon Sep 17 00:00:00 2001 From: doomedraven Date: Tue, 26 May 2026 11:14:34 +0200 Subject: [PATCH 15/29] fix Summary of Fixes Applied: 1. Fixed Installation Regression (installer/cape2.sh): * Removed the premature creation of $CAPE_ROOT and .venv from the dependencies() function. * Moved the uv venv creation to the install_CAPE() function, ensuring it happens after the repository is successfully cloned. This avoids the "destination path already exists" error during git clone. 2. Improved Argument Parsing (installer/cape2.sh): * Updated the IFACE_IP assignment logic to verify that the second argument is not a flag (e.g., --use-uv). This prevents flags from being incorrectly interpreted as IP addresses. 3. Code Cleanup (installer/cape2.sh): * Removed the unused sandbox_version transformation line, as the variable is no longer initialized or used within the script. These changes ensure the installation process is robust and that command-line arguments are handled correctly. --- installer/cape2.sh | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/installer/cape2.sh b/installer/cape2.sh index eb54908402b..7f0e08a64bb 100755 --- a/installer/cape2.sh +++ b/installer/cape2.sh @@ -1130,12 +1130,6 @@ function dependencies() { useradd --system -g ${USER} -d /home/${USER}/ -m ${USER} --shell /bin/bash fi - if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then - mkdir -p "$CAPE_ROOT" - chown ${USER}:${USER} "$CAPE_ROOT" - sudo -u ${USER} /usr/local/bin/uv venv "$CAPE_ROOT/.venv" - fi - echo "${USER} ALL=NOPASSWD: ${TCPDUMP_PATH}" > /etc/sudoers.d/tcpdump chmod 440 /etc/sudoers.d/tcpdump @@ -1362,6 +1356,9 @@ function install_CAPE() { git clone https://github.com/kevoreilly/CAPEv2/ "$CAPE_ROOT" fi chown ${USER}:${USER} -R "$CAPE_ROOT"/ + if [ "$USE_UV" = "true" ] || [ "$USE_UV" = "True" ]; then + sudo -u ${USER} /usr/local/bin/uv venv "$CAPE_ROOT/.venv" + fi #chown -R root:${USER} /usr/var/malheur/ #chmod -R =rwX,g=rwX,o=X /usr/var/malheur/ # Adapting owner permissions to the ${USER} path folder @@ -1731,7 +1728,7 @@ case $COMMAND in exit 0;; esac -if [ $# -ge 2 ]; then +if [ $# -ge 2 ] && [[ ! "$2" =~ ^-- ]]; then IFACE_IP=$2 elif [ $# -eq 0 ]; then echo "[-] check --help" @@ -1755,8 +1752,6 @@ for i in "$@"; do fi done -sandbox_version=$(echo "$sandbox_version"|tr "{A-Z}" "{a-z}") - #check if start with root if [ "$EUID" -ne 0 ] && [[ -z "${BUILD_ENV}" ]]; then echo 'This script must be run as root' From 3053a5bacd4eea2643ad8039c910fda40fb25f32 Mon Sep 17 00:00:00 2001 From: wmetcalf Date: Mon, 1 Jun 2026 19:56:34 +0000 Subject: [PATCH 16/29] feat(web): generic OIDC SSO via django-allauth (Okta/Azure/Auth0/Keycloak) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add optional OpenID Connect single sign-on, wired through allauth's generic `openid_connect` provider so it works with any OIDC-compliant IdP. Disabled by default; enable and configure via a new [oauth_oidc] section in web.conf. Builds on the per-user apikey app (PR #3053): with SSO on, DRF drops SessionAuthentication so an IdP-issued browser cookie can't authenticate API calls — scripts must present an explicit API key. Highlights: - CachedOpenIDConnect{Provider,OAuth2Adapter}: serves the OIDC discovery doc and JWKS from a process-wide TTL cache with bounded timeouts, issuer validation, and stale-on-error fallback, so a transient IdP blip no longer 500s the login. - MySocialAccountAdapter: email-domain allowlist (enforced every login), and optional IdP-group -> role mapping. is_staff/is_superuser are reconciled on EVERY login via a user_logged_in receiver (allauth only calls save_user at first provisioning), so removing a user from the admin group demotes them on next sign-in. Usernames are derived once and kept stable. - okta_user_sync management command (run via systemd timer): deactivates local users no longer ACTIVE in Okta and cascade-revokes their API keys, bounding the disable-to-revoke gap. Configured via admin_api_url/admin_api_token. - SECURE_PROXY_SSL_HEADER + USE_X_FORWARDED_HOST so the OIDC redirect_uri is built as https:// behind an nginx TLS-terminating proxy. - 8h sliding session lifetime (local_settings) to cap the SSO re-auth window. - Redesigned account / socialaccount templates with an SSO sign-in button. Notes for reviewers: - UI-internal /apiv2/ endpoints that are fetched with a browser cookie need a per-view @authentication_classes([SessionAuthentication]) once SSO is on; none are added here since that depends on which endpoints a deployment uses. - New on-login role reconciliation and the username-collision fallback want a test pass against a live IdP before this leaves draft. --- conf/default/web.conf.default | 45 ++- .../management/commands/okta_user_sync.py | 129 +++++++ web/templates/account/account_inactive.html | 15 +- web/templates/account/login.html | 64 +++- web/templates/account/logout.html | 29 +- web/templates/account/signup_closed.html | 16 +- .../socialaccount/authentication_error.html | 24 +- web/templates/socialaccount/signup.html | 41 ++- web/web/allauth_adapters.py | 330 +++++++++++++++++- web/web/local_settings.py | 27 +- web/web/settings.py | 79 ++++- 11 files changed, 703 insertions(+), 96 deletions(-) create mode 100644 web/apikey/management/commands/okta_user_sync.py diff --git a/conf/default/web.conf.default b/conf/default/web.conf.default index fe311627857..5bee340a903 100644 --- a/conf/default/web.conf.default +++ b/conf/default/web.conf.default @@ -7,8 +7,8 @@ enabled = no captcha = no 2fa = no # To enable Oauth check https://django-allauth.readthedocs.io and web/web/settings.py. -# Allow only SSO for users with specific domain. Can be allow to all if empty. -social_auth_email_domain = example.com +# Allow only SSO for users with a specific email domain. Leave blank to allow any. +social_auth_email_domain = [registration] enabled = no @@ -150,6 +150,47 @@ github = no gitlab = no twitter = no + +# OpenID Connect SSO. Generic — works with Okta / Azure AD / Auth0 / Google +# Workspace / Keycloak or any OIDC-compliant IdP via django-allauth's +# openid_connect provider. Set enabled = yes and fill in the fields below. +[oauth_oidc] +enabled = no +# Arbitrary identifier; becomes part of the callback URL: +# /accounts/oidc//login/callback/ +# Must match the redirect URI registered in your IdP app. +provider_id = oidc +# Display name shown on the login button. +name = SSO +# OAuth2 client credentials from your IdP app. +client_id = +client_secret = +# IdP discovery root — the /.well-known/openid-configuration is appended +# automatically. Examples: +# Okta: https:///oauth2/default +# Azure: https://login.microsoftonline.com//v2.0 +# Auth0: https:// +# Keycloak: https:///realms/ +server_url = +# Group-claim -> Django role mapping. The IdP must include group names in the +# ID token under `groups_claim` (default: "groups"). +# required_groups: if set, only users in at least one of these groups get a +# CAPE account provisioned. Leave blank to allow any IdP-authenticated user. +# admin_groups / superadmin_groups: membership maps to is_staff / is_superuser +# and is reconciled on every login (removal from the group demotes the user). +# Leave both blank to manage roles manually in Django. +required_groups = +admin_groups = +superadmin_groups = +groups_claim = groups +# Optional: periodic Okta account-status reconciliation (okta_user_sync +# management command, run via systemd timer). When a user with active API keys +# is no longer ACTIVE in Okta, they are deactivated locally and their keys are +# revoked. Requires an Okta admin API base URL and an SSWS token with the +# okta.users.read scope. Leave blank to disable. +admin_api_url = +admin_api_token = + [display_browser_martians] enabled = no diff --git a/web/apikey/management/commands/okta_user_sync.py b/web/apikey/management/commands/okta_user_sync.py new file mode 100644 index 00000000000..d395f5e0909 --- /dev/null +++ b/web/apikey/management/commands/okta_user_sync.py @@ -0,0 +1,129 @@ +"""Management command: reconcile local Django User.is_active with Okta status. + +For every Django User that has at least one active ApiKey AND a linked OIDC +SocialAccount, look the user up in Okta by email. If Okta no longer reports +the user as ACTIVE (status SUSPENDED, DEPROVISIONED, LOCKED_OUT, ...) or +returns no result for the email, deactivate the local user — which fires +the post_save cascade-revoke signal and invalidates all of their API keys. + +Run periodically via systemd timer (cape-okta-sync.timer) to bound the gap +between Okta-side disable/revoke and local API access being cut off. + +Configuration (web.conf [oauth_oidc]): + admin_api_url = https://.okta.com + admin_api_token = + +Usage: + poetry run python manage.py okta_user_sync # apply changes + poetry run python manage.py okta_user_sync --dry-run # report only +""" + +import logging + +import requests +from django.contrib.auth.models import User +from django.core.management.base import BaseCommand + +from allauth.socialaccount.models import SocialAccount +from apikey.models import ApiKey +from lib.cuckoo.common.config import Config + +log = logging.getLogger(__name__) + + +class Command(BaseCommand): + help = "Reconcile local User.is_active with Okta status; cascade-revoke ApiKeys for users no longer ACTIVE in Okta." + + def add_arguments(self, parser): + parser.add_argument( + "--dry-run", + action="store_true", + help="report what would change without modifying users", + ) + + def handle(self, *args, **opts): + web_cfg = Config("web") + oidc_cfg = getattr(web_cfg, "oauth_oidc", None) + if not oidc_cfg or not oidc_cfg.get("enabled", False): + self.stderr.write("[oauth_oidc] is not enabled — nothing to sync") + return + + admin_url = (oidc_cfg.get("admin_api_url") or "").rstrip("/") + admin_token = oidc_cfg.get("admin_api_token") or "" + if not admin_url or not admin_token: + self.stderr.write( + "[oauth_oidc] admin_api_url or admin_api_token missing — set them in web.conf to enable Okta sync" + ) + return + + # Find users that (a) have at least one active ApiKey and + # (b) have a linked SocialAccount (i.e., were JIT-provisioned via + # Okta SSO). Local-only admin accounts with API keys are skipped. + active_apikey_user_ids = set( + ApiKey.objects.filter(revoked_at__isnull=True).values_list("user_id", flat=True) + ) + sso_user_ids = set(SocialAccount.objects.values_list("user_id", flat=True)) + target_ids = active_apikey_user_ids & sso_user_ids + users = list(User.objects.filter(id__in=target_ids, is_active=True)) + if not users: + self.stdout.write("no SSO users with active API keys to check") + return + + self.stdout.write(f"checking {len(users)} SSO users with active ApiKeys against Okta") + + session = requests.Session() + session.headers.update( + { + "Authorization": f"SSWS {admin_token}", + "Accept": "application/json", + "User-Agent": "CAPE/okta_user_sync", + } + ) + + deactivated = 0 + for user in users: + email = (user.email or "").strip() + if not email: + self.stderr.write(f" user_id={user.id} ({user.username}): no email on local record — skipping") + continue + + try: + # search filter — exact email match. URL-quoting is handled by requests. + r = session.get( + f"{admin_url}/api/v1/users", + params={"search": f'profile.email eq "{email}"'}, + timeout=10, + ) + r.raise_for_status() + results = r.json() + except Exception as e: + self.stderr.write(f" {email}: Okta lookup failed: {e}") + continue + + if not isinstance(results, list) or not results: + # Email not found in Okta — user has been deleted there. + if self._deactivate(user, "okta_user_not_found", opts["dry_run"]): + deactivated += 1 + continue + + okta_user = results[0] + status = (okta_user.get("status") or "").upper() + if status != "ACTIVE": + if self._deactivate(user, f"okta_status_{status or 'UNKNOWN'}", opts["dry_run"]): + deactivated += 1 + else: + self.stdout.write(f" {email}: ACTIVE") + + suffix = " (dry run)" if opts["dry_run"] else "" + self.stdout.write(self.style.SUCCESS(f"sync complete{suffix} — {deactivated} user(s) deactivated")) + + def _deactivate(self, user, reason, dry_run): + msg = f" {user.email}: deactivating (reason={reason})" + if dry_run: + self.stdout.write(self.style.WARNING(f"{msg} [DRY RUN]")) + return False + user.is_active = False + user.save() # triggers the post_save cascade-revoke signal in apikey.signals + self.stdout.write(self.style.WARNING(msg)) + log.warning("okta_user_sync: deactivated user %s reason=%s", user.username, reason) + return True diff --git a/web/templates/account/account_inactive.html b/web/templates/account/account_inactive.html index 9612dcf6600..610bcb7e648 100644 --- a/web/templates/account/account_inactive.html +++ b/web/templates/account/account_inactive.html @@ -1,11 +1,18 @@ {% extends "base.html" %} - {% load i18n %} {% block head_title %}{% trans "Account Inactive" %}{% endblock %} {% block content %} -

{% trans "Account Inactive" %}

- -

{% trans "This account is inactive/Admin set manual approve." %}

+
+
+
+

{% trans "Account Inactive" %}

+
+
+

{% trans "This account is inactive. Contact an administrator if you believe this is in error." %}

+ {% trans "Back to login" %} +
+
+
{% endblock %} diff --git a/web/templates/account/login.html b/web/templates/account/login.html index 954e55d40c2..a5f42137e9f 100644 --- a/web/templates/account/login.html +++ b/web/templates/account/login.html @@ -1,18 +1,58 @@ {% extends 'base.html' %} {% load i18n %} {% load crispy_forms_tags %} -{% block title %}Log In{% endblock title %} +{% load socialaccount %} +{% block title %}Sign In{% endblock title %} + {% block content %} -
-

{% trans "Sign In" %}

-

- {% blocktrans %}If you have not created an account yet, then please Sign up first. - {% endblocktrans %} -

-
- {% csrf_token %} - {{ form|crispy }} - {% trans "Forgot password?" %} -
+
+
+
+

{% trans "Sign In" %}

+
+
+ + {% if messages %} + {% for m in messages %} +
{{ m }}
+ {% endfor %} + {% endif %} + + {% get_providers as socialaccount_providers %} + {% if socialaccount_providers %} +
+ {% for provider in socialaccount_providers %} + + + {% blocktrans with name=provider.name %}Sign in with {{ name }}{% endblocktrans %} + + {% endfor %} +
+
+
+ {% trans "or sign in locally" %} +
+
+ {% endif %} + +
+ {% csrf_token %} + {{ form|crispy }} +
+ +
+ +
+ +
+

+ {% blocktrans %}Don't have an account? Sign up.{% endblocktrans %} +

+
+
{% endblock content %} diff --git a/web/templates/account/logout.html b/web/templates/account/logout.html index 5dd48b0a6ea..eca1145da9b 100644 --- a/web/templates/account/logout.html +++ b/web/templates/account/logout.html @@ -1,14 +1,25 @@ {% extends 'base.html' %} +{% load i18n %} {% load crispy_forms_tags %} -{% block title %}Log Out{% endblock %} +{% block title %}{% trans "Sign Out" %}{% endblock %} + {% block content %} -
-

Log Out

-

Are you sure you want to log out?

-
- {% csrf_token %} - {{ form|crispy }} - -
+
+
+
+

{% trans "Sign Out" %}

+
+
+

{% trans "Are you sure you want to sign out?" %}

+
+ {% csrf_token %} + {{ form|crispy }} +
+ + {% trans "Cancel" %} +
+
+
+
{% endblock content %} diff --git a/web/templates/account/signup_closed.html b/web/templates/account/signup_closed.html index 9e8d44178b5..3ab65a4bc5c 100644 --- a/web/templates/account/signup_closed.html +++ b/web/templates/account/signup_closed.html @@ -1,11 +1,19 @@ {% extends "base.html" %} - {% load i18n %} {% block head_title %}{% trans "Sign Up Closed" %}{% endblock %} {% block content %} -

{% trans "Sign Up Closed" %}

- -

{% trans "We are sorry, but the sign up is currently closed." %}

+
+
+
+

{% trans "Sign Up Closed" %}

+
+
+

{% trans "We're sorry, but public sign-up is closed on this CAPE instance." %}

+

{% trans "If your organization uses single sign-on, return to the login page and click the SSO button — accounts are auto-provisioned for users your administrator has authorized." %}

+ {% trans "Back to login" %} +
+
+
{% endblock %} diff --git a/web/templates/socialaccount/authentication_error.html b/web/templates/socialaccount/authentication_error.html index bac67f17ad0..ad19eed4067 100644 --- a/web/templates/socialaccount/authentication_error.html +++ b/web/templates/socialaccount/authentication_error.html @@ -1,11 +1,25 @@ {% extends "base.html" %} - {% load i18n %} -{% block head_title %}{% trans "Social Network Login Failure" %}{% endblock %} +{% block head_title %}{% trans "Authentication Error" %}{% endblock %} {% block content %} -

{% trans "Social Network Login Failure" %}

- -

{% trans "An error occurred while attempting to login via your social network account." %}

+
+
+
+

{% trans "Authentication Error" %}

+
+
+ {% if reason %} +

{{ reason }}

+ {% else %} +

{% trans "An error occurred while attempting to sign in." %}

+ {% endif %} + {% if auth_error.code %} +

{% trans "Code:" %} {{ auth_error.code }}

+ {% endif %} + {% trans "Try again" %} +
+
+
{% endblock %} diff --git a/web/templates/socialaccount/signup.html b/web/templates/socialaccount/signup.html index 64d33fa13ea..2853f36d351 100644 --- a/web/templates/socialaccount/signup.html +++ b/web/templates/socialaccount/signup.html @@ -1,22 +1,33 @@ {% extends "base.html" %} - {% load i18n %} +{% load crispy_forms_tags %} -{% block head_title %}{% trans "Signup" %}{% endblock %} +{% block head_title %}{% trans "Sign Up" %}{% endblock %} {% block content %} -

{% trans "Sign Up" %}

- -

{% blocktrans with provider_name=account.get_provider.name site_name=site.name %}You are about to use your {{provider_name}} account to login to -{{site_name}}. As a final step, please complete the following form:{% endblocktrans %}

- - +
+
+
+

{% trans "Complete your account" %}

+
+
+

+ {% blocktrans with provider_name=account.get_provider.name %}You're signing in with {{provider_name}} for the first time. Confirm a couple of details to finish creating your CAPE account:{% endblocktrans %} +

+ +
+
+
{% endblock %} diff --git a/web/web/allauth_adapters.py b/web/web/allauth_adapters.py index 03942011e9d..4e1ec2ca475 100644 --- a/web/web/allauth_adapters.py +++ b/web/web/allauth_adapters.py @@ -1,10 +1,237 @@ +import logging +import requests +import threading +import time + from allauth.account.adapter import DefaultAccountAdapter +from allauth.core.exceptions import ImmediateHttpResponse from allauth.socialaccount.adapter import DefaultSocialAccountAdapter -from allauth.account.signals import email_confirmed, user_signed_up +from allauth.account.signals import email_confirmed, user_logged_in, user_signed_up from django import forms from django.conf import settings from django.contrib.auth.models import User from django.dispatch import receiver +from django.shortcuts import render + + +log = logging.getLogger(__name__) + + +# ── OIDC IdP-response cache ────────────────────────────────────────────────── +# allauth fetches the OIDC discovery document and the JWKS on every login +# (cached only per-adapter, i.e. per-request) with no timeouts. A transient +# IdP hiccup or slow response then surfaces as a 500 to the user. +# +# This cache makes both fetches: +# • process-wide with a 1-hour TTL (5 minutes for JWKS so key rotation is +# picked up quickly) +# • bounded by 5 s connect / 10 s read timeouts +# • served from a stale entry on transient fetch errors instead of crashing +# • issuer-validated per RFC 8414 §3 (discovery only) +# • double-checked-locked so concurrent cold-start requests still see fresh data + +_OIDC_CACHE: dict = {} +_OIDC_CACHE_LOCK = threading.Lock() +_DISCOVERY_TTL = 3600 +_JWKS_TTL = 300 + + +def _cached_fetch(cache_key: str, url: str, ttl: int, validate=None) -> dict: + """Fetch `url` as JSON with a process-wide TTL cache and stale-on-error. + + `validate(doc)` runs once on a freshly-fetched doc and may raise to reject + it; on rejection we fall back to the previously cached doc if any. + """ + now = time.monotonic() + with _OIDC_CACHE_LOCK: + entry = _OIDC_CACHE.get(cache_key) + if entry and (now - entry["ts"]) < ttl: + return entry["doc"] + + try: + resp = requests.get(url, timeout=(5, 10)) + resp.raise_for_status() + doc = resp.json() + if validate is not None: + validate(doc) + except (requests.RequestException, ValueError) as e: + if entry is not None: + log.warning( + "OIDC fetch failed for %s (%s); serving stale cached value", + url, e, + ) + return entry["doc"] + log.error("OIDC fetch failed for %s with no cached fallback: %s", url, e) + raise + + with _OIDC_CACHE_LOCK: + existing = _OIDC_CACHE.get(cache_key) + if not existing or (time.monotonic() - existing["ts"]) >= ttl: + _OIDC_CACHE[cache_key] = {"doc": doc, "ts": time.monotonic()} + else: + doc = existing["doc"] + + return doc + + +def _get_cached_openid_config(server_url: str) -> dict: + def _validate_issuer(doc): + expected = server_url.replace("/.well-known/openid-configuration", "").rstrip("/") + actual = (doc.get("issuer") or "").rstrip("/") + if actual and actual != expected: + raise ValueError( + f"OIDC discovery issuer mismatch: expected {expected!r}, got {actual!r}" + ) + + return _cached_fetch( + cache_key=f"discovery:{server_url}", + url=server_url, + ttl=_DISCOVERY_TTL, + validate=_validate_issuer, + ) + + +def _get_cached_jwks(jwks_url: str) -> dict: + return _cached_fetch( + cache_key=f"jwks:{jwks_url}", + url=jwks_url, + ttl=_JWKS_TTL, + ) + + +try: + from allauth.socialaccount.providers.openid_connect.views import ( + OpenIDConnectOAuth2Adapter as _BaseOIDCAdapter, + ) + from allauth.socialaccount.providers.openid_connect.provider import ( + OpenIDConnectProvider as _BaseOIDCProvider, + ) + from allauth.socialaccount.internal import jwtkit + + class CachedOpenIDConnectOAuth2Adapter(_BaseOIDCAdapter): + """Serves the OIDC discovery doc and JWKS from a process-level cache, + with bounded timeouts and stale-on-error fallback. Without this an IdP + blip produces a 500 on the login flow.""" + + @property + def openid_config(self): + if not hasattr(self, "_openid_config"): + self._openid_config = _get_cached_openid_config( + self.get_provider().server_url + ) + return self._openid_config + + def _decode_id_token(self, app, id_token): + # Mirror allauth's default but route JWKS through our cache. + verify_signature = not self.did_fetch_access_token + keys_url = self.openid_config["jwks_uri"] + issuer = self.openid_config["issuer"] + if not verify_signature: + return jwtkit.verify_and_decode( + credential=id_token, + keys_url=keys_url, + issuer=issuer, + audience=app.client_id, + lookup_kid=jwtkit.lookup_kid_jwk, + verify_signature=verify_signature, + ) + + import jwt as _jwt + header = _jwt.get_unverified_header(id_token) + kid = header["kid"] + alg = header["alg"] + keys_data = _get_cached_jwks(keys_url) + key = jwtkit.lookup_kid_jwk(keys_data, kid) + if key is None: + # cache miss on a freshly-rotated kid — force a refresh + with _OIDC_CACHE_LOCK: + _OIDC_CACHE.pop(f"jwks:{keys_url}", None) + keys_data = _get_cached_jwks(keys_url) + key = jwtkit.lookup_kid_jwk(keys_data, kid) + if key is None: + from allauth.socialaccount.providers.oauth2.client import OAuth2Error + raise OAuth2Error(f"Invalid 'kid': '{kid}'") + data = _jwt.decode( + id_token, key=key, algorithms=[alg], + issuer=issuer, audience=app.client_id, + leeway=30, + ) + jwtkit.verify_jti(data) + return data + + class CachedOpenIDConnectProvider(_BaseOIDCProvider): + """OpenID Connect provider using the cached adapter. + + Registered via SOCIALACCOUNT_PROVIDERS["openid_connect"]["provider_class"] + in settings.py — the officially-supported allauth override path. + """ + oauth2_adapter_class = CachedOpenIDConnectOAuth2Adapter + + @classmethod + def get_package(cls): + # allauth derives the URL module from get_package(); must point at + # the real openid_connect package so its urls.py is picked up by + # build_provider_urlpatterns(), not this module's package ("web"). + return "allauth.socialaccount.providers.openid_connect" + +except ImportError: + pass # openid_connect provider not installed — no-op + + +# ── Helpers ─────────────────────────────────────────────────────────────────── + +def _extract_groups(extra: dict) -> set: + """Return the set of IdP group names from token extra data.""" + oidc_cfg = getattr(settings, "OIDC_CFG", None) or {} + claim = oidc_cfg.get("groups_claim") or "groups" + raw = extra.get(claim) or [] + if isinstance(raw, str): + raw = [raw] + return {g for g in raw if isinstance(g, str)} + + +def _group_set(config_key: str) -> set: + """Parse a comma-separated group list from OIDC_CFG into a set.""" + oidc_cfg = getattr(settings, "OIDC_CFG", None) or {} + return { + g.strip() + for g in (oidc_cfg.get(config_key) or "").split(",") + if g.strip() + } + + +def _apply_idp_roles_and_email(user, extra: dict) -> bool: + """Reconcile a user's email and is_staff/is_superuser against the IdP's + claims/groups, mutating `user` in place. Returns True if anything changed + (the caller is responsible for saving). + + Role mapping is applied only when admin_groups / superadmin_groups are + configured; otherwise roles are left untouched so they can be managed + manually in Django. When configured, membership is authoritative — a user + removed from the admin group in the IdP is demoted on their next login. + """ + changed = False + + email = extra.get("email") or "" + if email and user.email != email: + user.email = email + changed = True + + admin_groups = _group_set("admin_groups") + super_groups = _group_set("superadmin_groups") + if admin_groups or super_groups: + user_groups = _extract_groups(extra) + new_staff = bool(user_groups & (admin_groups | super_groups)) + new_super = bool(user_groups & super_groups) + if user.is_staff != new_staff or user.is_superuser != new_super: + user.is_staff = new_staff + user.is_superuser = new_super + changed = True + + return changed + + +# ── Account adapters ────────────────────────────────────────────────────────── disposable_domain_list = [] if hasattr(settings, "DISPOSABLE_DOMAIN_LIST"): @@ -13,14 +240,11 @@ class DisposableEmails(DefaultAccountAdapter): - # https://fluffycloudsandlines.blog/using-django-allauth-for-google-login-to-any-django-app/ def clean_email(self, email): if email.rsplit("@", 1)[-1] in disposable_domain_list: raise forms.ValidationError("Admin banned disposable email services") - else: - return email + return email - # Enable/disable registration def is_open_for_signup(self, request): return settings.REGISTRATION_ENABLED @@ -39,23 +263,97 @@ def email_confirmed_(request, email_address, **kwargs): user.is_active = not settings.MANUAL_APPROVE user.save() + class MySocialAccountAdapter(DefaultSocialAccountAdapter): + def pre_social_login(self, request, sociallogin): - """ - Invoked just before a social login is about to proceed. - """ - user_email = sociallogin.account.extra_data.get("email") + """Reject IdP accounts whose email domain doesn't match the configured + allowlist. Silently skipped when social_auth_email_domain is blank. + + Raises ImmediateHttpResponse — caught by allauth's complete_login + wrapper and rendered as a user-facing error page (a bare + ValidationError here would bubble up as a 500).""" + user_email = sociallogin.account.extra_data.get("email") or "" if user_email and settings.SOCIAL_AUTH_EMAIL_DOMAIN: - domain = user_email.split("@")[1] + domain = user_email.rsplit("@", 1)[-1] if domain != settings.SOCIAL_AUTH_EMAIL_DOMAIN: - raise forms.ValidationError(f"Please use email with domain: {settings.SOCIAL_AUTH_EMAIL_DOMAIN}") + raise ImmediateHttpResponse( + render( + request, + "socialaccount/authentication_error.html", + {"reason": ( + f"Please use an email with domain: " + f"{settings.SOCIAL_AUTH_EMAIL_DOMAIN}" + )}, + ) + ) - def save_user(self, request, sociallogin, form=None): + def is_open_for_signup(self, request, sociallogin): + """Gate account provisioning on IdP group membership. + + When required_groups is blank, any IdP-authenticated user gets a CAPE + account (appropriate when the Okta app assignment is the access gate). + When set, only users in at least one listed group are provisioned — + useful when the app is assigned broadly but CAPE access should be + restricted to a subset. """ - Saves a new User instance using information provided from social account provider. + required = _group_set("required_groups") + if not required: + return True + return bool(_extract_groups(sociallogin.account.extra_data or {}) & required) + + def save_user(self, request, sociallogin, form=None): + """Provision a new SSO user: derive a stable username and apply the + initial email + IdP-group roles. + + This runs once, when the social identity is first linked. Email and + role reconciliation on *subsequent* logins is handled by the + ``_reconcile_sso_user_on_login`` receiver below — allauth does not call + save_user for returning users — so changes to a user's IdP group + membership (e.g. removal from an admin group) take effect on their next + sign-in. + + Username: derived from the email local-part. If that collides with an + existing account (two identities sharing a local-part across different + domains), a short suffix from the IdP subject claim — or the user's pk + if no subject is present — is appended to guarantee uniqueness. """ - user = super(MySocialAccountAdapter, self).save_user(request, sociallogin, form) - user.email = sociallogin.account.extra_data.get("email") - user.username = sociallogin.account.extra_data.get("email").split("@")[0] + user = super().save_user(request, sociallogin, form) + extra = sociallogin.account.extra_data or {} + + # ── username (provisioning only — kept stable across later logins) ── + identifier = ( + extra.get("email") + or extra.get("preferred_username") + or extra.get("sub") + or user.username + or "" + ) + if identifier: + base = identifier.split("@")[0] if "@" in identifier else identifier + if User.objects.filter(username=base).exclude(pk=user.pk).exists(): + suffix = (extra.get("sub") or "")[:8] or str(user.pk) + base = f"{base}_{suffix}" + user.username = base[:150] + + _apply_idp_roles_and_email(user, extra) user.save() return user + + +@receiver(user_logged_in) +def _reconcile_sso_user_on_login(sender, request, user, **kwargs): + """Reconcile email + IdP-group roles on every SSO login. + + allauth fires ``user_logged_in`` after a successful login and, for social + logins, passes the ``sociallogin``. Because ``save_user`` only runs at first + provisioning, this receiver is what makes role demotion/promotion and email + changes actually propagate when a returning user's IdP attributes change. + Local (non-SSO) logins carry no ``sociallogin`` and are left untouched. + """ + sociallogin = kwargs.get("sociallogin") + if sociallogin is None: + return + extra = getattr(sociallogin.account, "extra_data", None) or {} + if _apply_idp_roles_and_email(user, extra): + user.save() diff --git a/web/web/local_settings.py b/web/web/local_settings.py index 566f5b4e88f..73f2b27ede0 100644 --- a/web/web/local_settings.py +++ b/web/web/local_settings.py @@ -38,20 +38,13 @@ # STATIC_ROOT = "" # STATIC_ROOT = os.path.join(os.getcwd(), "static") -SOCIALACCOUNT_PROVIDERS = { - "google": { - "SCOPE": [ - "profile", - "email", - ], - "AUTH_PARAMS": { - "access_type": "online", - }, - }, - "github": { - "SCOPE": [ - "read:user", - "user:email", - ], - }, -} +# SOCIALACCOUNT_PROVIDERS removed: managed dynamically from web.conf [oauth_oidc] in settings.py. +# The previous stub here (google + github) was dead — the provider apps were commented out in INSTALLED_APPS. + +# Session lifetime: 8 hours absolute, slides on every request. +# Combined with django-allauth + Okta SSO, this forces a fresh Okta round-trip +# at most every 8h of activity (re-uses the existing Okta session if still +# valid, prompts otherwise). Helps cap the gap between Okta account +# disable/lockout and CAPE access being revoked. +SESSION_COOKIE_AGE = 28800 +SESSION_SAVE_EVERY_REQUEST = True diff --git a/web/web/settings.py b/web/web/settings.py index 4700988b975..754bbf442ab 100644 --- a/web/web/settings.py +++ b/web/web/settings.py @@ -17,6 +17,7 @@ RUNNING_TESTS = "test" in sys.argv +from django.core.exceptions import ImproperlyConfigured from lib.cuckoo.common.config import Config # In case we have VPNs enabled we need to initialize through the following @@ -279,17 +280,58 @@ "apikey", ] +# OpenID Connect (Okta / Azure AD / Auth0 / Google Workspace / Keycloak / +# any OIDC-compliant IdP) is wired through django-allauth's generic +# `openid_connect` provider — registered conditionally so the dependency +# stays inert when SSO is disabled. Configure via [oauth_oidc] in web.conf. +OIDC_CFG = getattr(web_cfg, "oauth_oidc", None) +if OIDC_CFG is not None and OIDC_CFG.get("enabled", False): + _missing = [k for k in ("client_id", "client_secret", "server_url") if not (OIDC_CFG.get(k) or "").strip()] + if _missing: + raise ImproperlyConfigured( + f"[oauth_oidc] enabled = yes but required fields are blank: {', '.join(_missing)}. Check conf/web.conf." + ) + INSTALLED_APPS.append("allauth.socialaccount.providers.openid_connect") + SOCIALACCOUNT_PROVIDERS = { + "openid_connect": { + # Use our subclass for process-level discovery-doc / JWKS caching. + "provider_class": "web.allauth_adapters.CachedOpenIDConnectProvider", + "APPS": [ + { + "provider_id": OIDC_CFG.get("provider_id", "oidc"), + "name": OIDC_CFG.get("name", "OIDC"), + "client_id": OIDC_CFG.get("client_id", ""), + "secret": OIDC_CFG.get("client_secret", ""), + "settings": { + "server_url": OIDC_CFG.get("server_url", ""), + }, + } + ], + } + } + AUDIT_FRAMEWORK = web_cfg.audit_framework.get("enabled", False) if api_cfg.api.token_auth_enabled: + # Per-user labeled API keys; ApiKeyAuthentication internally falls back to + # DRF's legacy TokenAuthentication so tokens issued via + # /apiv2/api-token-auth/ keep working without migration. + # + # When SSO is enabled, scripts targeting /apiv2/ MUST present an explicit + # API key (Authorization: Token ) — we drop SessionAuthentication from + # the default chain so a browser session cookie issued via the IdP can't + # authenticate API calls. Browser-only flows (apikey list/create pages, etc.) + # live outside DRF and continue to use Django session auth. Specific + # UI-internal endpoints that legitimately need cookie auth can opt back in + # per-view with @authentication_classes([SessionAuthentication]). + _api_auth_classes = ["apikey.authentication.ApiKeyAuthentication"] + if not (OIDC_CFG and OIDC_CFG.get("enabled", False)): + # No SSO configured — keep the legacy session-cookie fallback for + # back-compat with deployments that script against /apiv2/ using + # their browser cookies. + _api_auth_classes.append("rest_framework.authentication.SessionAuthentication") REST_FRAMEWORK = { - "DEFAULT_AUTHENTICATION_CLASSES": [ - # Per-user labeled API keys; internally falls back to DRF's legacy - # TokenAuthentication so tokens issued via /apiv2/api-token-auth/ - # keep working without migration. - "apikey.authentication.ApiKeyAuthentication", - "rest_framework.authentication.SessionAuthentication", - ], + "DEFAULT_AUTHENTICATION_CLASSES": _api_auth_classes, "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",), "DEFAULT_THROTTLE_CLASSES": ["apiv2.throttling.SubscriptionRateThrottle"], "DEFAULT_THROTTLE_RATES": { @@ -366,13 +408,18 @@ EMAIL_CONFIRMATION = web_cfg.registration.get("email_confirmation", False) SOCIAL_AUTH_EMAIL_DOMAIN = web_cfg.web_auth.get("social_auth_email_domain", False) -# be careful with SOCIALACCOUNT_AUTO_SIGNUP, if True, it will bypass custom sighup functions, default is True -# SOCIALACCOUNT_AUTO_SIGNUP = True +# Activate the social adapter so OIDC signups bypass the REGISTRATION_ENABLED +# toggle (the public-signup form gate). Users coming through the IdP have +# already been vetted and explicitly assigned to the app, so they shouldn't be +# blocked by the same flag that closes anonymous signup. The adapter also +# enforces the email-domain allowlist and IdP-group role mapping. +SOCIALACCOUNT_ADAPTER = "web.allauth_adapters.MySocialAccountAdapter" +SOCIALACCOUNT_AUTO_SIGNUP = True +# Send the user straight to the IdP when they click the SSO button, skipping +# allauth's intermediate confirmation page. (Login is initiated via GET.) +SOCIALACCOUNT_LOGIN_ON_GET = True # SOCIALACCOUNT_ONLY = True -# SOCIALACCOUNT_LOGIN_ON_GET=True # ACCOUNT_SIGNUP_FORM_CLASS = None -# In case you want to verify domain of email + set the username -# SOCIALACCOUNT_ADAPTER = 'web.allauth_adapters.MySocialAccountAdapter' # ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https" #### AllAuth end @@ -419,6 +466,14 @@ ALLOWED_HOSTS = ["*"] +# Reverse-proxy TLS termination: when nginx terminates HTTPS and forwards plain +# HTTP to gunicorn/daphne, Django must be told via X-Forwarded-Proto that the +# original request was HTTPS — otherwise request.is_secure() is False and the +# absolute OIDC redirect_uri django-allauth builds comes out as http://…, which +# the IdP rejects. Requires `proxy_set_header X-Forwarded-Proto $scheme;` in nginx. +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") +USE_X_FORWARDED_HOST = True + # Max size MAX_UPLOAD_SIZE = web_cfg.general.max_sample_size # Google's OAuth might need: "strict-origin-when-cross-origin" From 15db74930a6b9089878f568b12c6874be66ac132 Mon Sep 17 00:00:00 2001 From: wmetcalf Date: Tue, 2 Jun 2026 13:28:22 +0000 Subject: [PATCH 17/29] harden OIDC adapter and okta sync (review feedback) From the automated review on #3054: - pre_social_login fails closed when an email-domain allowlist is set but the IdP returns no email (previously the check was bypassed). - role reconciliation skips (rather than demoting) when the groups claim is entirely absent from the token; a present-but-empty claim still demotes. - _extract_groups ignores unexpected claim types instead of raising. - username derivation truncates the base before appending the uniqueness suffix, so it can't exceed 150 chars or lose the suffix that makes it unique. - _cached_fetch re-checks the cache under lock on fetch error, so a concurrent cold-start success is served instead of failing the login. - okta_user_sync escapes quotes/backslashes in the Okta search filter. --- .../management/commands/okta_user_sync.py | 7 +- web/web/allauth_adapters.py | 64 ++++++++++++++----- 2 files changed, 54 insertions(+), 17 deletions(-) diff --git a/web/apikey/management/commands/okta_user_sync.py b/web/apikey/management/commands/okta_user_sync.py index d395f5e0909..5401dfcd3ed 100644 --- a/web/apikey/management/commands/okta_user_sync.py +++ b/web/apikey/management/commands/okta_user_sync.py @@ -88,10 +88,13 @@ def handle(self, *args, **opts): continue try: - # search filter — exact email match. URL-quoting is handled by requests. + # search filter — exact email match. URL-quoting is handled by + # requests; escape backslashes and quotes so an address with + # those characters can't break the SCIM filter syntax. + safe_email = email.replace("\\", "\\\\").replace('"', '\\"') r = session.get( f"{admin_url}/api/v1/users", - params={"search": f'profile.email eq "{email}"'}, + params={"search": f'profile.email eq "{safe_email}"'}, timeout=10, ) r.raise_for_status() diff --git a/web/web/allauth_adapters.py b/web/web/allauth_adapters.py index 4e1ec2ca475..67d8e20faa3 100644 --- a/web/web/allauth_adapters.py +++ b/web/web/allauth_adapters.py @@ -55,12 +55,17 @@ def _cached_fetch(cache_key: str, url: str, ttl: int, validate=None) -> dict: if validate is not None: validate(doc) except (requests.RequestException, ValueError) as e: - if entry is not None: + # Re-read under the lock: another thread may have populated the cache + # while our fetch was in flight (concurrent cold start). Prefer any + # cached value — stale or freshly-won — over failing the login. + with _OIDC_CACHE_LOCK: + latest = _OIDC_CACHE.get(cache_key) + if latest is not None: log.warning( - "OIDC fetch failed for %s (%s); serving stale cached value", + "OIDC fetch failed for %s (%s); serving cached value", url, e, ) - return entry["doc"] + return latest["doc"] log.error("OIDC fetch failed for %s with no cached fallback: %s", url, e) raise @@ -187,6 +192,10 @@ def _extract_groups(extra: dict) -> set: raw = extra.get(claim) or [] if isinstance(raw, str): raw = [raw] + elif not isinstance(raw, (list, tuple, set)): + # Unexpected claim shape (int/bool/dict/…) — don't crash the login. + log.warning("OIDC groups claim %r has unexpected type %s; ignoring", claim, type(raw).__name__) + return set() return {g for g in raw if isinstance(g, str)} @@ -209,6 +218,11 @@ def _apply_idp_roles_and_email(user, extra: dict) -> bool: configured; otherwise roles are left untouched so they can be managed manually in Django. When configured, membership is authoritative — a user removed from the admin group in the IdP is demoted on their next login. + + Guard: if the groups claim is entirely *absent* from the token (scope or + claim-mapping misconfiguration, or a provider that drops it), role + reconciliation is skipped rather than silently demoting everyone. A present + but empty claim is honoured (the user really is in no groups → demote). """ changed = False @@ -220,13 +234,21 @@ def _apply_idp_roles_and_email(user, extra: dict) -> bool: admin_groups = _group_set("admin_groups") super_groups = _group_set("superadmin_groups") if admin_groups or super_groups: - user_groups = _extract_groups(extra) - new_staff = bool(user_groups & (admin_groups | super_groups)) - new_super = bool(user_groups & super_groups) - if user.is_staff != new_staff or user.is_superuser != new_super: - user.is_staff = new_staff - user.is_superuser = new_super - changed = True + oidc_cfg = getattr(settings, "OIDC_CFG", None) or {} + claim = oidc_cfg.get("groups_claim") or "groups" + if claim not in extra: + log.warning( + "OIDC groups claim %r absent from token for %s; skipping role reconciliation", + claim, user.username, + ) + else: + user_groups = _extract_groups(extra) + new_staff = bool(user_groups & (admin_groups | super_groups)) + new_super = bool(user_groups & super_groups) + if user.is_staff != new_staff or user.is_superuser != new_super: + user.is_staff = new_staff + user.is_superuser = new_super + changed = True return changed @@ -274,7 +296,17 @@ def pre_social_login(self, request, sociallogin): wrapper and rendered as a user-facing error page (a bare ValidationError here would bubble up as a 500).""" user_email = sociallogin.account.extra_data.get("email") or "" - if user_email and settings.SOCIAL_AUTH_EMAIL_DOMAIN: + if settings.SOCIAL_AUTH_EMAIL_DOMAIN: + if not user_email: + # Fail closed: a domain allowlist is configured but the IdP sent + # no email, so we can't verify the domain. Don't provision. + raise ImmediateHttpResponse( + render( + request, + "socialaccount/authentication_error.html", + {"reason": "An email address is required to sign in."}, + ) + ) domain = user_email.rsplit("@", 1)[-1] if domain != settings.SOCIAL_AUTH_EMAIL_DOMAIN: raise ImmediateHttpResponse( @@ -330,11 +362,13 @@ def save_user(self, request, sociallogin, form=None): or "" ) if identifier: - base = identifier.split("@")[0] if "@" in identifier else identifier + base = (identifier.split("@")[0] if "@" in identifier else identifier)[:150] if User.objects.filter(username=base).exclude(pk=user.pk).exists(): - suffix = (extra.get("sub") or "")[:8] or str(user.pk) - base = f"{base}_{suffix}" - user.username = base[:150] + # Reserve room for the suffix so truncation can't drop the bit + # that makes the name unique (and can't exceed the 150 limit). + suffix = "_" + ((extra.get("sub") or "")[:8] or str(user.pk)) + base = base[: 150 - len(suffix)] + suffix + user.username = base _apply_idp_roles_and_email(user, extra) user.save() From f2f266a8b4caadff02e0c6a74f1645a422587835 Mon Sep 17 00:00:00 2001 From: wmetcalf Date: Thu, 4 Jun 2026 14:49:54 +0000 Subject: [PATCH 18/29] review: address Copilot feedback (security hardening + apikey cleanups) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OIDC SSO: - gate SECURE_PROXY_SSL_HEADER / USE_X_FORWARDED_HOST behind a new [general] behind_proxy flag (default off) — trusting X-Forwarded-Proto/ Host is only safe behind a header-sanitizing reverse proxy. - pin id_token decoding to the provider's advertised signing algorithms (or an asymmetric default allowlist) instead of reflecting the token header's `alg`, closing algorithm-confusion edge cases. - render the domain-mismatch auth error as HTTP 403 and wrap the reason text in gettext; wrap the login page title in {% trans %}. - merge into SOCIALACCOUNT_PROVIDERS rather than reassigning it, so enabling OIDC can't clobber other configured providers. - correct the session-lifetime comment (8h sliding idle timeout, not "8h absolute"). apikey app (folded in here since #3053 already merged): - escapejs the key name in the revoke confirm() prompt (was an XSS / string-break vector if a name contained a quote). - move _user_may_manage_keys into apikey/policy.py so the context processor no longer imports from views (decouples, avoids cycle risk). - reference apikey.apps.ApiKeyConfig explicitly in INSTALLED_APPS so the disable-cascade signal wiring in ready() always loads. --- conf/default/web.conf.default | 7 ++++ web/apikey/context_processors.py | 4 +-- web/apikey/policy.py | 19 +++++++++++ web/apikey/templates/apikey/list.html | 2 +- web/apikey/views.py | 16 +-------- web/templates/account/login.html | 2 +- web/web/allauth_adapters.py | 22 +++++++++---- web/web/local_settings.py | 11 ++++--- web/web/settings.py | 47 +++++++++++++++------------ 9 files changed, 79 insertions(+), 51 deletions(-) create mode 100644 web/apikey/policy.py diff --git a/conf/default/web.conf.default b/conf/default/web.conf.default index 5bee340a903..91ef9af6239 100644 --- a/conf/default/web.conf.default +++ b/conf/default/web.conf.default @@ -30,6 +30,13 @@ disposable_domain_list = data/safelist/disposable_domain_list.txt [general] timezone = UTC +# Set to yes only when CAPE is served behind a reverse proxy (e.g. nginx) that +# terminates TLS and strips/overwrites X-Forwarded-Proto / X-Forwarded-Host from +# clients. Enables Django's SECURE_PROXY_SSL_HEADER + USE_X_FORWARDED_HOST so +# request.is_secure() and absolute URLs (incl. the OIDC redirect_uri) are correct. +# Leave no for a directly-exposed app — trusting forwarded headers would allow +# proto/host spoofing. +behind_proxy = no # Prescan new file tasks with YARA for sample identification and custom execution # Useful to set options, tags, timeout, etc for packers/obfuscators/cryptors yara_recon = no diff --git a/web/apikey/context_processors.py b/web/apikey/context_processors.py index 9d389cbdf52..7298c618524 100644 --- a/web/apikey/context_processors.py +++ b/web/apikey/context_processors.py @@ -1,9 +1,9 @@ """Template context processor — surfaces apikey access policy to templates.""" -from .views import _user_may_manage_keys +from .policy import user_may_manage_keys def apikey_access(request): """Make `may_manage_apikeys` available to every template, so the user dropdown can hide the API Keys link for SSO non-staff users.""" - return {"may_manage_apikeys": _user_may_manage_keys(getattr(request, "user", None))} + return {"may_manage_apikeys": user_may_manage_keys(getattr(request, "user", None))} diff --git a/web/apikey/policy.py b/web/apikey/policy.py new file mode 100644 index 00000000000..00c6e7dae96 --- /dev/null +++ b/web/apikey/policy.py @@ -0,0 +1,19 @@ +"""API-key access policy — kept view-independent so both views.py and the +context processor can import it without pulling in view-layer dependencies +(or risking an import cycle).""" + +from allauth.socialaccount.models import SocialAccount + + +def user_may_manage_keys(user): + """Return True if `user` is allowed to view/create/revoke their own keys. + Local-only users always pass; SSO-provisioned users must be staff.""" + if not user or not user.is_authenticated: + return False + # Called from the apikey_access context processor on every page load — + # cache the SocialAccount lookup on the user object for the request to + # avoid a redundant query per render. + if not hasattr(user, "_may_manage_keys"): + is_sso = SocialAccount.objects.filter(user=user).exists() + user._may_manage_keys = True if not is_sso else bool(user.is_staff) + return user._may_manage_keys diff --git a/web/apikey/templates/apikey/list.html b/web/apikey/templates/apikey/list.html index 21f8d53ca3f..c4b15225630 100644 --- a/web/apikey/templates/apikey/list.html +++ b/web/apikey/templates/apikey/list.html @@ -55,7 +55,7 @@
Key created {% if not k.revoked_at %} -
+ {% csrf_token %}
diff --git a/web/apikey/views.py b/web/apikey/views.py index 9064f5045ca..8eaa3553a1b 100644 --- a/web/apikey/views.py +++ b/web/apikey/views.py @@ -15,7 +15,6 @@ their behalf, or admin creates a key for them in Django admin). """ -from allauth.socialaccount.models import SocialAccount from django.contrib import messages from django.contrib.auth.decorators import login_required from django.shortcuts import get_object_or_404, redirect, render @@ -24,20 +23,7 @@ from .forms import ApiKeyCreateForm from .models import ApiKey - - -def _user_may_manage_keys(user): - """Return True if `user` is allowed to view/create/revoke their own keys. - Local-only users always pass; SSO-provisioned users must be staff.""" - if not user or not user.is_authenticated: - return False - # Called from the apikey_access context processor on every page load — - # cache the SocialAccount lookup on the user object for the request to - # avoid a redundant query per render. - if not hasattr(user, "_may_manage_keys"): - is_sso = SocialAccount.objects.filter(user=user).exists() - user._may_manage_keys = True if not is_sso else bool(user.is_staff) - return user._may_manage_keys +from .policy import user_may_manage_keys as _user_may_manage_keys def _forbidden(request): diff --git a/web/templates/account/login.html b/web/templates/account/login.html index a5f42137e9f..0e8a099b876 100644 --- a/web/templates/account/login.html +++ b/web/templates/account/login.html @@ -2,7 +2,7 @@ {% load i18n %} {% load crispy_forms_tags %} {% load socialaccount %} -{% block title %}Sign In{% endblock title %} +{% block title %}{% trans "Sign In" %}{% endblock title %} {% block content %}
diff --git a/web/web/allauth_adapters.py b/web/web/allauth_adapters.py index 67d8e20faa3..5d39e5b1b96 100644 --- a/web/web/allauth_adapters.py +++ b/web/web/allauth_adapters.py @@ -12,6 +12,7 @@ from django.contrib.auth.models import User from django.dispatch import receiver from django.shortcuts import render +from django.utils.translation import gettext as _ log = logging.getLogger(__name__) @@ -34,6 +35,10 @@ _OIDC_CACHE_LOCK = threading.Lock() _DISCOVERY_TTL = 3600 _JWKS_TTL = 300 +# Asymmetric signing algorithms accepted for ID tokens when the provider's +# discovery doc doesn't advertise `id_token_signing_alg_values_supported`. +# Deliberately excludes "none" and the HMAC family to avoid algorithm-confusion. +_DEFAULT_OIDC_ALGS = ["RS256", "RS384", "RS512", "ES256", "ES384", "ES512", "PS256", "PS384", "PS512"] def _cached_fetch(cache_key: str, url: str, ttl: int, validate=None) -> dict: @@ -144,7 +149,6 @@ def _decode_id_token(self, app, id_token): import jwt as _jwt header = _jwt.get_unverified_header(id_token) kid = header["kid"] - alg = header["alg"] keys_data = _get_cached_jwks(keys_url) key = jwtkit.lookup_kid_jwk(keys_data, kid) if key is None: @@ -156,8 +160,12 @@ def _decode_id_token(self, app, id_token): if key is None: from allauth.socialaccount.providers.oauth2.client import OAuth2Error raise OAuth2Error(f"Invalid 'kid': '{kid}'") + # Pin accepted algorithms to the provider's advertised set rather + # than reflecting the token header's untrusted `alg`. PyJWT rejects + # a token whose alg isn't in this list, blocking "none"/HS* confusion. + allowed_algs = self.openid_config.get("id_token_signing_alg_values_supported") or _DEFAULT_OIDC_ALGS data = _jwt.decode( - id_token, key=key, algorithms=[alg], + id_token, key=key, algorithms=allowed_algs, issuer=issuer, audience=app.client_id, leeway=30, ) @@ -304,7 +312,8 @@ def pre_social_login(self, request, sociallogin): render( request, "socialaccount/authentication_error.html", - {"reason": "An email address is required to sign in."}, + {"reason": _("An email address is required to sign in.")}, + status=403, ) ) domain = user_email.rsplit("@", 1)[-1] @@ -313,10 +322,9 @@ def pre_social_login(self, request, sociallogin): render( request, "socialaccount/authentication_error.html", - {"reason": ( - f"Please use an email with domain: " - f"{settings.SOCIAL_AUTH_EMAIL_DOMAIN}" - )}, + {"reason": _("Please use an email with domain: %(domain)s") + % {"domain": settings.SOCIAL_AUTH_EMAIL_DOMAIN}}, + status=403, ) ) diff --git a/web/web/local_settings.py b/web/web/local_settings.py index 73f2b27ede0..41c6fd2c0ef 100644 --- a/web/web/local_settings.py +++ b/web/web/local_settings.py @@ -41,10 +41,11 @@ # SOCIALACCOUNT_PROVIDERS removed: managed dynamically from web.conf [oauth_oidc] in settings.py. # The previous stub here (google + github) was dead — the provider apps were commented out in INSTALLED_APPS. -# Session lifetime: 8 hours absolute, slides on every request. -# Combined with django-allauth + Okta SSO, this forces a fresh Okta round-trip -# at most every 8h of activity (re-uses the existing Okta session if still -# valid, prompts otherwise). Helps cap the gap between Okta account -# disable/lockout and CAPE access being revoked. +# Session lifetime: 8-hour sliding idle timeout. SESSION_SAVE_EVERY_REQUEST +# resets the SESSION_COOKIE_AGE window on each request, so the session expires +# only after 8h of *inactivity* (not 8h absolute). Combined with django-allauth +# + Okta SSO, an idle user is forced back through Okta, capping the gap between +# an Okta account disable/lockout and CAPE access being revoked. Note: saving +# the session every request adds session-store writes; fine for this scale. SESSION_COOKIE_AGE = 28800 SESSION_SAVE_EVERY_REQUEST = True diff --git a/web/web/settings.py b/web/web/settings.py index 754bbf442ab..1d8fe108a43 100644 --- a/web/web/settings.py +++ b/web/web/settings.py @@ -276,8 +276,9 @@ "rest_framework.authtoken", # Per-user labeled API keys (multi-key, individually revocable). Lives # alongside DRF's legacy `authtoken` so ApiKeyAuthentication can fall - # back to existing tokens for back-compat. - "apikey", + # back to existing tokens for back-compat. Reference the AppConfig + # explicitly so its ready() (disable-cascade signal wiring) always loads. + "apikey.apps.ApiKeyConfig", ] # OpenID Connect (Okta / Azure AD / Auth0 / Google Workspace / Keycloak / @@ -292,22 +293,23 @@ f"[oauth_oidc] enabled = yes but required fields are blank: {', '.join(_missing)}. Check conf/web.conf." ) INSTALLED_APPS.append("allauth.socialaccount.providers.openid_connect") - SOCIALACCOUNT_PROVIDERS = { - "openid_connect": { - # Use our subclass for process-level discovery-doc / JWKS caching. - "provider_class": "web.allauth_adapters.CachedOpenIDConnectProvider", - "APPS": [ - { - "provider_id": OIDC_CFG.get("provider_id", "oidc"), - "name": OIDC_CFG.get("name", "OIDC"), - "client_id": OIDC_CFG.get("client_id", ""), - "secret": OIDC_CFG.get("client_secret", ""), - "settings": { - "server_url": OIDC_CFG.get("server_url", ""), - }, - } - ], - } + # Merge into any existing provider config instead of reassigning, so + # enabling OIDC doesn't clobber other providers a deployment may have set. + SOCIALACCOUNT_PROVIDERS = globals().get("SOCIALACCOUNT_PROVIDERS") or {} + SOCIALACCOUNT_PROVIDERS["openid_connect"] = { + # Use our subclass for process-level discovery-doc / JWKS caching. + "provider_class": "web.allauth_adapters.CachedOpenIDConnectProvider", + "APPS": [ + { + "provider_id": OIDC_CFG.get("provider_id", "oidc"), + "name": OIDC_CFG.get("name", "OIDC"), + "client_id": OIDC_CFG.get("client_id", ""), + "secret": OIDC_CFG.get("client_secret", ""), + "settings": { + "server_url": OIDC_CFG.get("server_url", ""), + }, + } + ], } AUDIT_FRAMEWORK = web_cfg.audit_framework.get("enabled", False) @@ -471,8 +473,13 @@ # original request was HTTPS — otherwise request.is_secure() is False and the # absolute OIDC redirect_uri django-allauth builds comes out as http://…, which # the IdP rejects. Requires `proxy_set_header X-Forwarded-Proto $scheme;` in nginx. -SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") -USE_X_FORWARDED_HOST = True +# +# Gated behind [general] behind_proxy because trusting X-Forwarded-Proto/Host is +# only safe when a reverse proxy strips/overwrites those headers from clients — +# enabling them with a directly-reachable app would allow proto/host spoofing. +if web_cfg.general.get("behind_proxy", False): + SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") + USE_X_FORWARDED_HOST = True # Max size MAX_UPLOAD_SIZE = web_cfg.general.max_sample_size From 187ead7b528d3791f4d54c20a70df32d7f4f5d7b Mon Sep 17 00:00:00 2001 From: doomedraven Date: Sun, 7 Jun 2026 19:17:08 +0200 Subject: [PATCH 19/29] Ensure machine cleanup and handle unexpected errors Improve AnalysisManager cleanup and error handling: track dead machines so stop/release are only attempted for healthy machines (moved stop/release into finally guarded by is_dead), log and set TASK_FAILED_ANALYSIS on unexpected exceptions during launch_analysis, and unlock the machine before re-raising. Prevent guest file path collisions by storing samples in a per-task subdirectory (use task id in temp path). Add tests to verify final cleanup on unhandled exceptions and that unexpected analysis errors set task status and unlock machines; update imports accordingly. --- lib/cuckoo/core/analysis_manager.py | 46 ++++++++++++++++++----------- lib/cuckoo/core/guest.py | 5 ++-- tests/test_analysis_manager.py | 45 +++++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 21 deletions(-) diff --git a/lib/cuckoo/core/analysis_manager.py b/lib/cuckoo/core/analysis_manager.py index b7f406295e6..6b398703419 100644 --- a/lib/cuckoo/core/analysis_manager.py +++ b/lib/cuckoo/core/analysis_manager.py @@ -309,6 +309,7 @@ def category_checks(self) -> Optional[bool]: def machine_running(self) -> Generator[None, None, None]: assert self.machinery_manager and self.machine and self.guest + is_dead = False try: with self.db.session.begin(): self.machinery_manager.start_machine(self.machine) @@ -319,6 +320,7 @@ def machine_running(self) -> Generator[None, None, None]: self.dump_machine_memory() except (CuckooMachineError, CuckooGuestCriticalTimeout) as e: + is_dead = True # This machine has turned dead, so we'll throw an exception # which informs the AnalysisManager that it should analyze # this task again with another available machine. @@ -337,25 +339,26 @@ def machine_running(self) -> Generator[None, None, None]: shutil.rmtree(self.storage) raise CuckooDeadMachine(self.machine.name) from e + finally: + if not is_dead: + with self.db.session.begin(): + try: + self.machinery_manager.stop_machine(self.machine) + except CuckooMachineError as e: + self.log.warning("Unable to stop machine %s: %s", self.machine.label, e) + # Explicitly rollback since we don't re-raise the exception. + self.db.session.rollback() - with self.db.session.begin(): - try: - self.machinery_manager.stop_machine(self.machine) - except CuckooMachineError as e: - self.log.warning("Unable to stop machine %s: %s", self.machine.label, e) - # Explicitly rollback since we don't re-raise the exception. - self.db.session.rollback() - - try: - # Release the analysis machine, but only if the machine is not dead. - with self.db.session.begin(): - self.machinery_manager.machinery.release(self.machine) - except CuckooMachineError as e: - self.log.error( - "Unable to release machine %s, reason %s. You might need to restore it manually", - self.machine.label, - e, - ) + try: + # Release the analysis machine, but only if the machine is not dead. + with self.db.session.begin(): + self.machinery_manager.machinery.release(self.machine) + except CuckooMachineError as e: + self.log.error( + "Unable to release machine %s, reason %s. You might need to restore it manually", + self.machine.label, + e, + ) def dump_machine_memory(self) -> None: if not self.cfg.cuckoo.memory_dump and not self.task.memory: @@ -482,6 +485,13 @@ def launch_analysis(self) -> None: # Put the task back in pending so that the schedule can attempt to choose a new machine. self.db.set_status(self.task.id, TASK_PENDING) raise + except Exception as e: + self.log.exception("Unexpected exception during analysis: %s", e) + with self.db.session.begin(): + self.db.set_status(self.task.id, TASK_FAILED_ANALYSIS) + if hasattr(self, "machine") and self.machine: + self.db.unlock_machine(self.machine) + raise else: with self.db.session.begin(): self.db.set_status(self.task.id, TASK_COMPLETED) diff --git a/lib/cuckoo/core/guest.py b/lib/cuckoo/core/guest.py index 2a3de974af1..537d916df5b 100644 --- a/lib/cuckoo/core/guest.py +++ b/lib/cuckoo/core/guest.py @@ -327,10 +327,11 @@ def start_analysis(self, options): # If the target is a file, upload it to the guest. if options["category"] in ("file", "archive"): # Use the correct os.sep in the filepath based on what OS this file is destined for + # Store the sample in a unique per-task subdirectory instead of %TEMP%\ to prevent guest path collisions if self.platform == "windows": - filepath = ntpath.join(self.determine_temp_path(), sanitize_filename(options["file_name"])) + filepath = ntpath.join(self.determine_temp_path(), str(options["id"]), sanitize_filename(options["file_name"])) else: - filepath = os.path.join(self.determine_temp_path(), sanitize_filename(options["file_name"])) + filepath = os.path.join(self.determine_temp_path(), str(options["id"]), sanitize_filename(options["file_name"])) data = {"filepath": filepath} files = { "file": ("sample.bin", open(sample_path, "rb")), diff --git a/tests/test_analysis_manager.py b/tests/test_analysis_manager.py index e283558d2ed..9760b87c5e5 100644 --- a/tests/test_analysis_manager.py +++ b/tests/test_analysis_manager.py @@ -12,7 +12,7 @@ from lib.cuckoo.common.abstracts import Machinery from lib.cuckoo.common.config import Config, ConfigMeta from lib.cuckoo.core.analysis_manager import AnalysisManager -from lib.cuckoo.core.data.task import TASK_RUNNING, Task +from lib.cuckoo.core.data.task import TASK_RUNNING, Task, TASK_FAILED_ANALYSIS from lib.cuckoo.core.data.guests import Guest from lib.cuckoo.core.data.machines import Machine from lib.cuckoo.core.database import _Database @@ -440,3 +440,46 @@ class mock_sample: assert analysis_man.init_storage() is True mocker.patch("lib.cuckoo.core.database._Database.view_sample", return_value=mock_sample()) assert analysis_man.category_checks() is True + + def test_machine_running_finally_cleanup( + self, db: _Database, task: Task, machine: Machine, machinery_manager: MachineryManager, mocker: MockerFixture + ): + """Verify that machine_running stops and releases the machine even if an unhandled exception is raised in yield.""" + analysis_man = AnalysisManager(task=task, machine=machine, machinery_manager=machinery_manager) + + # Mock machinery manager functions + mock_start = mocker.patch.object(machinery_manager, "start_machine") + mock_stop = mocker.patch.object(machinery_manager, "stop_machine") + mock_release = mocker.patch.object(machinery_manager.machinery, "release") + + guest = Guest() + guest.id = 123 + analysis_man.guest = guest + + with pytest.raises(RuntimeError, match="Simulated unhandled exception"): + with analysis_man.machine_running(): + raise RuntimeError("Simulated unhandled exception") + + # Verify stop and release are still cleanly called on unhandled exceptions + assert mock_start.called + assert mock_stop.called + assert mock_release.called + + def test_launch_analysis_unexpected_exception( + self, db: _Database, task: Task, machine: Machine, machinery_manager: MachineryManager, mocker: MockerFixture + ): + """Verify that launch_analysis handles unexpected exceptions by setting status to failed and unlocking the machine.""" + analysis_man = AnalysisManager(task=task, machine=machine, machinery_manager=machinery_manager) + + # Force perform_analysis to raise an unhandled exception + mocker.patch.object(analysis_man, "perform_analysis", side_effect=RuntimeError("Unexpected perform_analysis error")) + mock_unlock = mocker.patch.object(db, "unlock_machine") + + with pytest.raises(RuntimeError, match="Unexpected perform_analysis error"): + analysis_man.launch_analysis() + + # Verify task is flagged failed and machine is unlocked + with db.session.begin(): + db.session.refresh(task) + assert task.status == TASK_FAILED_ANALYSIS + assert mock_unlock.called From 668b2765e0623004b5185bf8c548364f04d9a02c Mon Sep 17 00:00:00 2001 From: doomedraven Date: Sun, 7 Jun 2026 21:27:07 +0200 Subject: [PATCH 20/29] Use task-scoped file paths; simplify stop_machine Prefix uploaded sample paths with a task-id subdirectory for file/archive analyses by updating options['file_name'] (sanitized) before add_config, using platform-appropriate separators. The guest upload now builds the destination filepath directly from options['file_name'], preventing guest path collisions and ensuring analysis.conf contains the correct path. Also simplify AnalysisManager teardown: move the DB session context into a try block and catch CuckooMachineError outside it to log failures to stop the machine without performing an explicit manual rollback. --- lib/cuckoo/core/analysis_manager.py | 10 ++++------ lib/cuckoo/core/guest.py | 13 ++++++++++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/cuckoo/core/analysis_manager.py b/lib/cuckoo/core/analysis_manager.py index 6b398703419..4831b7b30c0 100644 --- a/lib/cuckoo/core/analysis_manager.py +++ b/lib/cuckoo/core/analysis_manager.py @@ -341,13 +341,11 @@ def machine_running(self) -> Generator[None, None, None]: raise CuckooDeadMachine(self.machine.name) from e finally: if not is_dead: - with self.db.session.begin(): - try: + try: + with self.db.session.begin(): self.machinery_manager.stop_machine(self.machine) - except CuckooMachineError as e: - self.log.warning("Unable to stop machine %s: %s", self.machine.label, e) - # Explicitly rollback since we don't re-raise the exception. - self.db.session.rollback() + except CuckooMachineError as e: + self.log.warning("Unable to stop machine %s: %s", self.machine.label, e) try: # Release the analysis machine, but only if the machine is not dead. diff --git a/lib/cuckoo/core/guest.py b/lib/cuckoo/core/guest.py index 537d916df5b..15e3c446cfa 100644 --- a/lib/cuckoo/core/guest.py +++ b/lib/cuckoo/core/guest.py @@ -308,6 +308,14 @@ def start_analysis(self, options): # Upload the analyzer. self.upload_analyzer() + # Update file_name in options if category is file/archive to include task-id unique subdirectory + # This must be done BEFORE self.add_config(options) is called so that analysis.conf in guest has the correct path + if options["category"] in ("file", "archive"): + if self.platform == "windows": + options["file_name"] = f"{options['id']}\\{sanitize_filename(options['file_name'])}" + else: + options["file_name"] = f"{options['id']}/{sanitize_filename(options['file_name'])}" + # Pass along the analysis.conf file. self.add_config(options) # Allow Auxiliary modules to prepare the Guest. @@ -327,11 +335,10 @@ def start_analysis(self, options): # If the target is a file, upload it to the guest. if options["category"] in ("file", "archive"): # Use the correct os.sep in the filepath based on what OS this file is destined for - # Store the sample in a unique per-task subdirectory instead of %TEMP%\ to prevent guest path collisions if self.platform == "windows": - filepath = ntpath.join(self.determine_temp_path(), str(options["id"]), sanitize_filename(options["file_name"])) + filepath = ntpath.join(self.determine_temp_path(), options["file_name"]) else: - filepath = os.path.join(self.determine_temp_path(), str(options["id"]), sanitize_filename(options["file_name"])) + filepath = os.path.join(self.determine_temp_path(), options["file_name"]) data = {"filepath": filepath} files = { "file": ("sample.bin", open(sample_path, "rb")), From a6285c03ff14a870355ba6c17659d730b3e4ffbb Mon Sep 17 00:00:00 2001 From: doomedraven Date: Sun, 7 Jun 2026 21:31:48 +0200 Subject: [PATCH 21/29] fix --- lib/cuckoo/core/analysis_manager.py | 2 +- tests/test_analysis_manager.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/cuckoo/core/analysis_manager.py b/lib/cuckoo/core/analysis_manager.py index 4831b7b30c0..0dc4fd428f9 100644 --- a/lib/cuckoo/core/analysis_manager.py +++ b/lib/cuckoo/core/analysis_manager.py @@ -22,7 +22,7 @@ from lib.cuckoo.common.path_utils import path_delete, path_exists, path_mkdir from lib.cuckoo.common.utils import convert_to_printable, create_folder, get_memdump_path from lib.cuckoo.core.database import Database, _Database -from lib.cuckoo.core.data.task import TASK_COMPLETED, TASK_PENDING, TASK_RUNNING, Task +from lib.cuckoo.core.data.task import TASK_COMPLETED, TASK_PENDING, TASK_RUNNING, TASK_FAILED_ANALYSIS, Task from lib.cuckoo.core.data.machines import Machine from lib.cuckoo.core.data.guests import Guest from lib.cuckoo.core.guest import GuestManager diff --git a/tests/test_analysis_manager.py b/tests/test_analysis_manager.py index 9760b87c5e5..902048a8a8d 100644 --- a/tests/test_analysis_manager.py +++ b/tests/test_analysis_manager.py @@ -446,12 +446,12 @@ def test_machine_running_finally_cleanup( ): """Verify that machine_running stops and releases the machine even if an unhandled exception is raised in yield.""" analysis_man = AnalysisManager(task=task, machine=machine, machinery_manager=machinery_manager) - + # Mock machinery manager functions mock_start = mocker.patch.object(machinery_manager, "start_machine") mock_stop = mocker.patch.object(machinery_manager, "stop_machine") mock_release = mocker.patch.object(machinery_manager.machinery, "release") - + guest = Guest() guest.id = 123 analysis_man.guest = guest @@ -470,7 +470,7 @@ def test_launch_analysis_unexpected_exception( ): """Verify that launch_analysis handles unexpected exceptions by setting status to failed and unlocking the machine.""" analysis_man = AnalysisManager(task=task, machine=machine, machinery_manager=machinery_manager) - + # Force perform_analysis to raise an unhandled exception mocker.patch.object(analysis_man, "perform_analysis", side_effect=RuntimeError("Unexpected perform_analysis error")) mock_unlock = mocker.patch.object(db, "unlock_machine") From a67eb16d97fef1062afccb8aec9023995f15c94f Mon Sep 17 00:00:00 2001 From: doomedraven Date: Sun, 7 Jun 2026 21:36:14 +0200 Subject: [PATCH 22/29] Update test_analysis_manager.py --- tests/test_analysis_manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_analysis_manager.py b/tests/test_analysis_manager.py index 902048a8a8d..fefb8aba2cd 100644 --- a/tests/test_analysis_manager.py +++ b/tests/test_analysis_manager.py @@ -474,6 +474,7 @@ def test_launch_analysis_unexpected_exception( # Force perform_analysis to raise an unhandled exception mocker.patch.object(analysis_man, "perform_analysis", side_effect=RuntimeError("Unexpected perform_analysis error")) mock_unlock = mocker.patch.object(db, "unlock_machine") + mock_log_exception = mocker.patch.object(analysis_man.log, "exception") with pytest.raises(RuntimeError, match="Unexpected perform_analysis error"): analysis_man.launch_analysis() @@ -483,3 +484,4 @@ def test_launch_analysis_unexpected_exception( db.session.refresh(task) assert task.status == TASK_FAILED_ANALYSIS assert mock_unlock.called + assert mock_log_exception.called From 7089dcdaedc1f073b64ed26e039d36a5f4cde74c Mon Sep 17 00:00:00 2001 From: doomedraven Date: Sun, 7 Jun 2026 21:41:23 +0200 Subject: [PATCH 23/29] Update test_analysis_manager.py --- tests/test_analysis_manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_analysis_manager.py b/tests/test_analysis_manager.py index fefb8aba2cd..4bfa2b1d04f 100644 --- a/tests/test_analysis_manager.py +++ b/tests/test_analysis_manager.py @@ -481,7 +481,7 @@ def test_launch_analysis_unexpected_exception( # Verify task is flagged failed and machine is unlocked with db.session.begin(): - db.session.refresh(task) - assert task.status == TASK_FAILED_ANALYSIS + db_task = db.view_task(task.id) + assert db_task.status == TASK_FAILED_ANALYSIS assert mock_unlock.called assert mock_log_exception.called From f7c7a5751b07c1e98612efc5ec995d27b358de5c Mon Sep 17 00:00:00 2001 From: doomedraven Date: Sun, 7 Jun 2026 21:46:49 +0200 Subject: [PATCH 24/29] Update test_analysis_manager.py --- tests/test_analysis_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_analysis_manager.py b/tests/test_analysis_manager.py index 4bfa2b1d04f..ecba0946608 100644 --- a/tests/test_analysis_manager.py +++ b/tests/test_analysis_manager.py @@ -473,7 +473,7 @@ def test_launch_analysis_unexpected_exception( # Force perform_analysis to raise an unhandled exception mocker.patch.object(analysis_man, "perform_analysis", side_effect=RuntimeError("Unexpected perform_analysis error")) - mock_unlock = mocker.patch.object(db, "unlock_machine") + mock_unlock = mocker.patch("lib.cuckoo.core.analysis_manager.Database.unlock_machine") mock_log_exception = mocker.patch.object(analysis_man.log, "exception") with pytest.raises(RuntimeError, match="Unexpected perform_analysis error"): From 65f5bc1d596a9f7546d677beb72849f5d22579ff Mon Sep 17 00:00:00 2001 From: doomedraven Date: Sun, 7 Jun 2026 21:54:17 +0200 Subject: [PATCH 25/29] Update test_analysis_manager.py --- tests/test_analysis_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_analysis_manager.py b/tests/test_analysis_manager.py index ecba0946608..1cebffe6794 100644 --- a/tests/test_analysis_manager.py +++ b/tests/test_analysis_manager.py @@ -473,7 +473,7 @@ def test_launch_analysis_unexpected_exception( # Force perform_analysis to raise an unhandled exception mocker.patch.object(analysis_man, "perform_analysis", side_effect=RuntimeError("Unexpected perform_analysis error")) - mock_unlock = mocker.patch("lib.cuckoo.core.analysis_manager.Database.unlock_machine") + mock_unlock = mocker.patch("lib.cuckoo.core.database._Database.unlock_machine") mock_log_exception = mocker.patch.object(analysis_man.log, "exception") with pytest.raises(RuntimeError, match="Unexpected perform_analysis error"): From 4eb823eb947e01fb6449b0553a8b59f40eea9104 Mon Sep 17 00:00:00 2001 From: doomedraven Date: Sun, 7 Jun 2026 21:58:30 +0200 Subject: [PATCH 26/29] Update test_analysis_manager.py --- tests/test_analysis_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_analysis_manager.py b/tests/test_analysis_manager.py index 1cebffe6794..b4ad3626e62 100644 --- a/tests/test_analysis_manager.py +++ b/tests/test_analysis_manager.py @@ -452,7 +452,7 @@ def test_machine_running_finally_cleanup( mock_stop = mocker.patch.object(machinery_manager, "stop_machine") mock_release = mocker.patch.object(machinery_manager.machinery, "release") - guest = Guest() + guest = mocker.MagicMock() guest.id = 123 analysis_man.guest = guest From eedf8c6f7a96f2d9b05f43aa02d5cb7ad88928aa Mon Sep 17 00:00:00 2001 From: doomedraven Date: Mon, 8 Jun 2026 07:13:10 +0000 Subject: [PATCH 27/29] Refactor demux.py for improved file handling and type hints Added optional parameter to demux_sflock and updated return types for better clarity. Enhanced file handling logic and added new patterns for executable identification. --- lib/cuckoo/common/demux.py | 374 ++++++++++++++++++++++++++----------- 1 file changed, 263 insertions(+), 111 deletions(-) diff --git a/lib/cuckoo/common/demux.py b/lib/cuckoo/common/demux.py index 91718c29518..790f926cc78 100644 --- a/lib/cuckoo/common/demux.py +++ b/lib/cuckoo/common/demux.py @@ -2,10 +2,11 @@ # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org # See the file 'docs/LICENSE' for copying permission. +import re import logging import os import tempfile -from typing import Any, Dict, List, Tuple +from typing import Any, Dict, List, Tuple, Optional from lib.cuckoo.common.config import Config from lib.cuckoo.common.exceptions import CuckooDemuxError @@ -123,6 +124,109 @@ ] +IGNORABLE_PATTERNS = ( + re.compile(br"msvcp\d+\.dll$", re.IGNORECASE), + re.compile(br"vcruntime\d+(_\d)?\.dll$", re.IGNORECASE), + re.compile(br"api-ms-win-.*\.dll$", re.IGNORECASE), + re.compile(br"ucrtbase\.dll$", re.IGNORECASE), + re.compile(br"concrt\d+\.dll$", re.IGNORECASE), + re.compile(br"vccorlib\d+\.dll$", re.IGNORECASE), + # You can add more patterns here, e.g., for .NET runtimes: + # re.compile(r"coreclr\.dll$", re.IGNORECASE), + # re.compile(r"System\..*\.dll$", re.IGNORECASE), +) + +EXE_PREFERENCE_LIST = ( + b"setup.exe", + b"install.exe", + b"installer.exe", + b"main.exe", +) + +FILE_EXT_OF_INTEREST = ( + b".bat", + b".cmd", + b".dat", + b".db", + # b".dll", + b".doc", + b".exe", + b".html", + b".js", + b".jse", + b".lnk", + b".msi", + b".ps1", + b".scr", + b".temp", + b".tmp", + b".vbe", + b".vbs", + b".wsf", + b".xls", +) + + +def find_payload_to_run(file_list: List[str]) -> Optional[str]: + """ + Analyzes a list of filenames to find the most likely executable payload. + + Args: + file_list: A list of filenames (e.g., from zipfile.namelist()) + + Returns: + The filename of the executable to run, or None if no payload is found. + """ + + executables_found = [] + unknown_other_files = [] + ignorable_files_found = 0 + + for filename in file_list: + # Skip empty names (can happen with directory entries) + if not filename: + continue + + # --- Step 1: Check against Ignorable Patterns --- + is_ignorable = False + for pattern in IGNORABLE_PATTERNS: + if pattern.match(filename): + is_ignorable = True + ignorable_files_found += 1 + break + + if is_ignorable: + continue # It's a known runtime DLL, skip to the next file + + # --- Step 2: Check for Executable --- + if filename.lower().endswith(FILE_EXT_OF_INTEREST): + executables_found.append(filename.decode()) + continue + + # --- Step 3: It's an unknown file --- + # (e.g., sqlite.dll, ._, config.ini, etc.) + unknown_other_files.append(filename.decode()) + + # --- Triage Decision Logic --- + + # Case 1: No executables found at all. + if not executables_found: + log.debug("No executables found. Ignored %d runtime files.", ignorable_files_found) + return [] # -> Decision: SKIP this archive + + # Case 2: Multiple executables found. Use heuristics. + log.debug("Found multiple executables: %s", str(executables_found)) + + # Check against our preferred list + for preferred_name in EXE_PREFERENCE_LIST: + for exe_name in executables_found: + if exe_name.lower() == preferred_name: + log.debug("Heuristic: Choosing '%s' from preference list.", exe_name) + return [exe_name.decode()] # -> Decision: RUN this preferred file + + # if no prefered name, return all files of interest + return executables_found + def options2passwd(options: str) -> str: password = "" if "password=" in options: @@ -220,11 +324,15 @@ def _sf_children(child: Any) -> Tuple[bytes, str, str, int]: return path_to_extract, child.platform, child.magic or "", child.filesize -def demux_sflock(filename: bytes, options: str, check_shellcode: bool = True) -> Tuple[List[Tuple[bytes, str, str, int]], str]: +# ToDo fix typing need to add str as error msg +def demux_sflock( + filename: bytes, options: str, check_shellcode: bool = True +) -> Tuple[List[Tuple[bytes, str, str, int]], str, List[str]]: retlist = [] + submit_opts = [] # do not extract from .bin (downloaded from us) if os.path.splitext(filename)[1] == b".bin": - return retlist, "" + return retlist, "", submit_opts # ToDo need to introduce error msgs here try: @@ -234,7 +342,7 @@ def demux_sflock(filename: bytes, options: str, check_shellcode: bool = True) -> # Before unpacking, ensure the file actually exists and is not empty to avoid IncorrectUsageException if not path_exists(filename) or os.path.getsize(filename) == 0: - return [(filename, platform, magic_type, file_size)], "file not found or empty" + return [(filename, platform, magic_type, file_size)], "file not found or empty", [] password = options2passwd(options) or "infected" try: @@ -247,9 +355,48 @@ def demux_sflock(filename: bytes, options: str, check_shellcode: bool = True) -> magic_type = file.get_type() or "" platform = file.get_platform() file_size = file.get_size() - return [(filename, platform, magic_type, file_size)], "" + return [(filename, platform, magic_type, file_size)], "", [] if unpacked.package in blacklist_extensions: - return [], "blacklisted package" + return retlist, "blacklisted package", submit_opts + + if unpacked.filepaths: + # CASE 1: Archive contains multiple files + if len(unpacked.filepaths) > 1: + # Find interesting files (exe/dll) and submit the ORIGINAL archive + # with instructions to run specifically those files. + execs = find_payload_to_run(unpacked.filepaths) + submit_opts = [f"file={runable}" for runable in execs] + # returning empty retlist so it will use parent file + return [], "", submit_opts + + # CASE 2: Archive contains exactly 1 file + single_file = unpacked.filepaths[0] + # assuming unpacked.children matches unpacked.filepaths indices + # we get the object representing this single file + current_child = unpacked.children[0] if unpacked.children else None + + if current_child: + if single_file.endswith((b".7z", b".rar", b".zip")): + # It's a sub-archive. We need to go one level deeper. + # We loop through THIS sub-archive's children. + # Note: Ensure 'current_child.children' exists in your object model. + # If 'unpacked.children' already contained the deep files, this loop might need adjusting based on your specific API. + execs = find_payload_to_run(current_child.filepaths) + if execs: + extracted = _sf_children(current_child) + path = extracted[0] + if path: + submit_opts += [f"file={runable}" for runable in execs] + retlist.append(extracted) + else: + # It's just a single regular file (e.g., malware.exe inside a zip). + # Extract and add to task. + extracted = _sf_children(current_child) + path = extracted[0] + if path: + retlist.append(extracted) + return retlist, "", submit_opts + for sf_child in unpacked.children: if sf_child.to_dict().get("children"): for ch in sf_child.children: @@ -271,7 +418,29 @@ def demux_sflock(filename: bytes, options: str, check_shellcode: bool = True) -> retlist.append(tmp_child) except Exception as e: log.exception(e) - return list(filter(None, retlist)), "" + return list(filter(None, retlist)), "", submit_opts + + +def _prepare_file(filename: bytes, options: str = "", size: int = 0) -> Tuple[Optional[bytes], Optional[str]]: + try: + f_basename = os.path.basename(filename).decode('utf-8', errors='replace') + except Exception: + f_basename = "unknown_filename" + + if not size: + size = File(filename).get_size() + if size <= web_cfg.general.max_sample_size: + return filename, None + + safe_options = options or "" + if web_cfg.general.allow_ignore_size and "ignore_size_check" in safe_options: + return filename, None + + if web_cfg.general.enable_trim and trim_file(filename): + return trimmed_path(filename), None + + error_msg = f"File too big ({f_basename}), enable 'allow_ignore_size' in web.conf or use 'ignore_size_check' option" + return None, error_msg def demux_sample( @@ -297,121 +466,104 @@ def demux_sample( error_list = [] retlist = [] - # if a package was specified, trim if allowed and required + + # --- 1. Quick Handle: Specific Package --- if package: - if package in ("msix",): - retlist.append((filename, "windows")) + if package == "msix": + return [(filename, "windows")], [] + + valid_file, error = _prepare_file(filename, options) + if valid_file: + return [(valid_file, platform)], [] else: - if File(filename).get_size() <= web_cfg.general.max_sample_size or ( - web_cfg.general.allow_ignore_size and "ignore_size_check" in options - ): - retlist.append((filename, platform)) - else: - if web_cfg.general.enable_trim and trim_file(filename): - retlist.append((trimmed_path(filename), platform)) - else: - error_list.append( - { - os.path.basename( - filename - ).decode(): "File too big, enable 'allow_ignore_size' in web.conf or use 'ignore_size_check' option" - } - ) - return retlist, error_list + return [], [{os.path.basename(filename).decode(errors='ignore'): error}] - # handle quarantine files + # --- 2. File Preparation (Unquarantine) --- filename = unquarantine(filename) # don't try to extract from office docs magic = File(filename).get_type() or "" - # if file is an Office doc and password is supplied, try to decrypt the doc - if "Microsoft" in magic: - pass - # ignore = {"Outlook", "Message", "Disk Image"} - elif any(x in magic for x in OFFICE_TYPES): - password = options2passwd(options) or None - if use_sflock: - if HAS_SFLOCK: - retlist = demux_office(filename, password, platform) - return retlist, error_list - else: - log.error("Detected password protected office file, but no sflock is installed: poetry install") - error_list.append( - { - os.path.basename( - filename - ).decode(): "Detected password protected office file, but no sflock is installed or correct password provided" - } - ) - - # don't try to extract from Java archives or executables - if ( - "Java Jar" in magic - or "Java archive data" in magic - or "PE32" in magic - or "MS-DOS executable" in magic - or any(x in magic for x in File.LINUX_TYPES) - ): - retlist = [] - if File(filename).get_size() <= web_cfg.general.max_sample_size or ( - web_cfg.general.allow_ignore_size and "ignore_size_check" in options - ): - retlist.append((filename, platform)) + + # --- 3. Handle Password-Protected Office Files --- + is_office = "Microsoft" in magic or any(x in magic for x in OFFICE_TYPES) + if is_office and use_sflock: + password = options2passwd(options) + if HAS_SFLOCK and password: + retlist = demux_office(filename, password, platform) + return retlist, error_list else: - if web_cfg.general.enable_trim and trim_file(filename): - retlist.append((trimmed_path(filename), platform)) - else: - error_list.append( - { - os.path.basename( - filename - ).decode(): "File too big, enable 'allow_ignore_size' in web.conf or use 'ignore_size_check' option", - } - ) + log.error("Detected password protected office file, but no sflock is installed.") + return [], [{os.path.basename(filename).decode(errors='ignore'): "Detected password protected office file, but no sflock is installed"}] + + # --- 4. Skip Extraction for specific types --- + ignored_signatures = [ + "Java Jar", + "Java archive data", + "PE32", + "MS-DOS executable" + ] + # Añadimos tipos de Linux si están definidos + if hasattr(File, "LINUX_TYPES"): + ignored_signatures.extend(File.LINUX_TYPES) + + # If magic matches an ignored type, treat as regular file and skip sflock + if any(sig in magic for sig in ignored_signatures): + valid_file, error = _prepare_file(filename, options) + if valid_file: + return [(valid_file, platform)], [] + else: + return [], [{os.path.basename(filename).decode(errors='ignore'): error}] + + # --- 5. Generic Extraction (Sflock) --- + check_shellcode = "check_shellcode=0" not in (options or "") + + # Inicializamos variables de retorno de sflock + extracted_files = [] + sflock_error = "" + execs = [] + + if HAS_SFLOCK and use_sflock: + extracted_files, sflock_error, execs = demux_sflock(filename, options, check_shellcode) + + # Si sflock encontró ejecutables específicos que quiere forzar (ej. payload en un zip) + if execs: + error_list.extend(execs) + + # If nothing extracted (not an archive, or sflock failed/skipped) + if not extracted_files: + if sflock_error: + error_list.append({os.path.basename(filename).decode(errors='ignore'): sflock_error}) + + # Fallback: submit the original file + valid_file, error = _prepare_file(filename, options) + if valid_file: + retlist.append((valid_file, platform)) + elif error and not sflock_error: + # Only report size error if sflock didn't already report a more relevant error + error_list.append({os.path.basename(filename).decode(errors='ignore'): error}) + return retlist, error_list - new_retlist = [] + # --- 6. Process Extracted Files --- + for block in extracted_files: + if len(retlist) >= demux_files_limit: + break - check_shellcode = True - if options and "check_shellcode=0" in options: - check_shellcode = False + ex_filename, ex_platform, ex_magic, ex_size = block + f_basename_str = os.path.basename(ex_filename).decode(errors='ignore') - # all in one unarchiver - retlist, error_msg = demux_sflock(filename, options, check_shellcode) if HAS_SFLOCK and use_sflock else ([], "") - # if it isn't a ZIP or an email, or we aren't able to obtain anything interesting from either, then just submit the - # original file - if not retlist: - if error_msg: - error_list.append({os.path.basename(filename).decode(): error_msg}) - new_retlist.append((filename, platform)) - else: - for entry in retlist: - if not isinstance(entry, (list, tuple)) or len(entry) < 2: - log.warning("Skipping invalid entry in retlist: %s", entry) - continue - filename, platform = entry[0], entry[1] - magic_type = entry[2] if len(entry) > 2 else "" - file_size = entry[3] if len(entry) > 3 else 0 - # verify not Windows binaries here: - if platform == "linux" and not linux_enabled and "Python" not in magic_type: - error_list.append({os.path.basename(filename).decode(): "Linux processing is disabled"}) - continue - - if file_size > web_cfg.general.max_sample_size: - if web_cfg.general.allow_ignore_size and "ignore_size_check" in options: - if web_cfg.general.enable_trim: - # maybe identify here - if trim_file(filename): - filename = trimmed_path(filename) - else: - error_list.append( - { - os.path.basename( - filename - ).decode(): "File too big, enable 'allow_ignore_size' in web.conf or use 'ignore_size_check' option", - } - ) - new_retlist.append((filename, platform)) + # Platform check (Linux) + if ex_platform == "linux" and not linux_enabled and "Python" not in ex_magic: + error_list.append({f_basename_str: "Linux processing is disabled"}) + continue - return new_retlist[:demux_files_limit], error_list + # Validate size and trim if necessary + # Pass 'ex_size' to _prepare_file to avoid re-calculating it + valid_file, error = _prepare_file(ex_filename, options, size=ex_size) + + if valid_file: + retlist.append((valid_file, ex_platform)) + else: + error_list.append({f_basename_str: error}) + return retlist, error_list From bc0dade61f2655b8ac6244752dc71d73f1f89bdc Mon Sep 17 00:00:00 2001 From: doomedraven Date: Mon, 8 Jun 2026 08:15:46 +0000 Subject: [PATCH 28/29] Refactor find_payload_to_run to return list of executables --- lib/cuckoo/common/demux.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/cuckoo/common/demux.py b/lib/cuckoo/common/demux.py index 790f926cc78..5275b721593 100644 --- a/lib/cuckoo/common/demux.py +++ b/lib/cuckoo/common/demux.py @@ -167,7 +167,7 @@ ) -def find_payload_to_run(file_list: List[str]) -> Optional[str]: +def find_payload_to_run(file_list: List[Any]) -> List[str]: """ Analyzes a list of filenames to find the most likely executable payload. @@ -175,7 +175,7 @@ def find_payload_to_run(file_list: List[str]) -> Optional[str]: file_list: A list of filenames (e.g., from zipfile.namelist()) Returns: - The filename of the executable to run, or None if no payload is found. + A list of filenames of the executables to run. """ executables_found = [] @@ -187,10 +187,13 @@ def find_payload_to_run(file_list: List[str]) -> Optional[str]: if not filename: continue + # Ensure filename is bytes to match regexes and endswith accurately + filename_bytes = filename if isinstance(filename, bytes) else filename.encode() + # --- Step 1: Check against Ignorable Patterns --- is_ignorable = False for pattern in IGNORABLE_PATTERNS: - if pattern.match(filename): + if pattern.match(filename_bytes): is_ignorable = True ignorable_files_found += 1 break @@ -199,20 +202,19 @@ def find_payload_to_run(file_list: List[str]) -> Optional[str]: continue # It's a known runtime DLL, skip to the next file # --- Step 2: Check for Executable --- - if filename.lower().endswith(FILE_EXT_OF_INTEREST): - executables_found.append(filename.decode()) + if filename_bytes.lower().endswith(FILE_EXT_OF_INTEREST): + executables_found.append(filename_bytes) continue # --- Step 3: It's an unknown file --- - # (e.g., sqlite.dll, ._, config.ini, etc.) - unknown_other_files.append(filename.decode()) + unknown_other_files.append(filename_bytes) # --- Triage Decision Logic --- # Case 1: No executables found at all. if not executables_found: log.debug("No executables found. Ignored %d runtime files.", ignorable_files_found) - return [] # -> Decision: SKIP this archive + return [] # Case 2: Multiple executables found. Use heuristics. log.debug("Found multiple executables: %s", str(executables_found)) @@ -222,10 +224,10 @@ def find_payload_to_run(file_list: List[str]) -> Optional[str]: for exe_name in executables_found: if exe_name.lower() == preferred_name: log.debug("Heuristic: Choosing '%s' from preference list.", exe_name) - return [exe_name.decode()] # -> Decision: RUN this preferred file + return [exe_name.decode(errors="ignore")] - # if no prefered name, return all files of interest - return executables_found + # if no preferred name, return all files of interest + return [exe.decode(errors="ignore") for exe in executables_found] def options2passwd(options: str) -> str: password = "" @@ -381,7 +383,7 @@ def demux_sflock( # We loop through THIS sub-archive's children. # Note: Ensure 'current_child.children' exists in your object model. # If 'unpacked.children' already contained the deep files, this loop might need adjusting based on your specific API. - execs = find_payload_to_run(current_child.filepaths) + execs = find_payload_to_run(getattr(current_child, "filepaths", [])) if execs: extracted = _sf_children(current_child) path = extracted[0] @@ -527,7 +529,8 @@ def demux_sample( # Si sflock encontró ejecutables específicos que quiere forzar (ej. payload en un zip) if execs: - error_list.extend(execs) + for opt in execs: + error_list.append({"option": opt}) # If nothing extracted (not an archive, or sflock failed/skipped) if not extracted_files: From 0bd9d37019a0a1abfae799e909b8429e48c7a4fa Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Wed, 10 Jun 2026 11:48:28 +0000 Subject: [PATCH 29/29] ci: Update requirements.txt --- requirements.txt | 840 +++++++++++++++++++++++------------------------ 1 file changed, 403 insertions(+), 437 deletions(-) diff --git a/requirements.txt b/requirements.txt index df039da0189..23603b775bd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,127 +1,127 @@ aiohappyeyeballs==2.6.1 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558 \ --hash=sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 -aiohttp==3.13.4 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:014dcc10ec8ab8db681f0d68e939d1e9286a5aa2b993cbbdb0db130853e02144 \ - --hash=sha256:0bc0a5cf4f10ef5a2c94fdde488734b582a3a7a000b131263e27c9295bd682d9 \ - --hash=sha256:0c0c7c07c4257ef3a1df355f840bc62d133bcdef5c1c5ba75add3c08553e2eed \ - --hash=sha256:0c296f1221e21ba979f5ac1964c3b78cfde15c5c5f855ffd2caab337e9cd9182 \ - --hash=sha256:0ce692c3468fa831af7dceed52edf51ac348cebfc8d3feb935927b63bd3e8576 \ - --hash=sha256:0d0dbc6c76befa76865373d6aa303e480bb8c3486e7763530f7f6e527b471118 \ - --hash=sha256:0e217cf9f6a42908c52b46e42c568bd57adc39c9286ced31aaace614b6087965 \ - --hash=sha256:0e5d701c0aad02a7dce72eef6b93226cf3734330f1a31d69ebbf69f33b86666e \ - --hash=sha256:10fb7b53262cf4144a083c9db0d2b4d22823d6708270a9970c4627b248c6064c \ - --hash=sha256:13168f5645d9045522c6cef818f54295376257ed8d02513a37c2ef3046fc7a97 \ - --hash=sha256:13a5cc924b59859ad2adb1478e31f410a7ed46e92a2a619d6d1dd1a63c1a855e \ - --hash=sha256:153274535985a0ff2bff1fb6c104ed547cec898a09213d21b0f791a44b14d933 \ - --hash=sha256:1746338dc2a33cf706cd7446575d13d451f28f9860bebc908c7632b22e71ae3f \ - --hash=sha256:1867087e2c1963db1216aedf001efe3b129835ed2b05d97d058176a6d08b5726 \ - --hash=sha256:19f60011ad60e40a01d242238bb335399e3a4d8df958c63cbb835add8d5c3b5a \ - --hash=sha256:1c946f10f413836f82ea4cfb90200d2a59578c549f00857e03111cf45ad01ca5 \ - --hash=sha256:1db491abe852ca2fa6cc48a3341985b0174b3741838e1341b82ac82c8bd9e871 \ - --hash=sha256:2062f675f3fe6e06d6113eb74a157fb9df58953ffed0cdb4182554b116545758 \ - --hash=sha256:20af8aad61d1803ff11152a26146d8d81c266aa8c5aa9b4504432abb965c36a0 \ - --hash=sha256:26ed03f7d3d6453634729e2c7600d7255d65e879559c5a48fe1bb78355cde74b \ - --hash=sha256:29be00c51972b04bf9d5c8f2d7f7314f48f96070ca40a873a53056e652e805f7 \ - --hash=sha256:2d15e7e4f1099d9e4d863eaf77a8eee5dcb002b7d7188061b0fbee37f845899e \ - --hash=sha256:2d5bea57be7aca98dbbac8da046d99b5557c5cf4e28538c4c786313078aca09e \ - --hash=sha256:320e40192a2dcc1cf4b5576936e9652981ab596bf81eb309535db7e2f5b5672f \ - --hash=sha256:3262386c4ff370849863ea93b9ea60fd59c6cf56bf8f93beac625cf4d677c04d \ - --hash=sha256:34e89912b6c20e0fd80e07fa401fd218a410aa1ce9f1c2f1dad6db1bd0ce0927 \ - --hash=sha256:351f3171e2458da3d731ce83f9e6b9619e325c45cbd534c7759750cabf453ad7 \ - --hash=sha256:358a6af0145bc4dda037f13167bef3cce54b132087acc4c295c739d05d16b1c3 \ - --hash=sha256:383880f7b8de5ac208fa829c7038d08e66377283b2de9e791b71e06e803153c2 \ - --hash=sha256:3b4e07d8803a70dd886b5f38588e5b49f894995ca8e132b06c31a2583ae2ef6e \ - --hash=sha256:3cdd3393130bf6588962441ffd5bde1d3ea2d63a64afa7119b3f3ba349cebbe7 \ - --hash=sha256:3d1ba8afb847ff80626d5e408c1fdc99f942acc877d0702fe137015903a220a9 \ - --hash=sha256:42adaeea83cbdf069ab94f5103ce0787c21fb1a0153270da76b59d5578302329 \ - --hash=sha256:45abbbf09a129825d13c18c7d3182fecd46d9da3cfc383756145394013604ac1 \ - --hash=sha256:463fa18a95c5a635d2b8c09babe240f9d7dbf2a2010a6c0b35d8c4dff2a0e819 \ - --hash=sha256:473bb5aa4218dd254e9ae4834f20e31f5a0083064ac0136a01a62ddbae2eaa42 \ - --hash=sha256:48708e2706106da6967eff5908c78ca3943f005ed6bcb75da2a7e4da94ef8c70 \ - --hash=sha256:49f0b18a9b05d79f6f37ddd567695943fcefb834ef480f17a4211987302b2dc7 \ - --hash=sha256:4a31c0c587a8a038f19a4c7e60654a6c899c9de9174593a13e7cc6e15ff271f9 \ - --hash=sha256:4b061e7b5f840391e3f64d0ddf672973e45c4cfff7a0feea425ea24e51530fc2 \ - --hash=sha256:4baa48ce49efd82d6b1a0be12d6a36b35e5594d1dd42f8bfba96ea9f8678b88c \ - --hash=sha256:4c3f733916e85506b8000dddc071c6b82f8c68f56c99adb328d6550017db062d \ - --hash=sha256:4e2e68085730a03704beb2cff035fa8648f62c9f93758d7e6d70add7f7bb5b3b \ - --hash=sha256:534913dfb0a644d537aebb4123e7d466d94e3be5549205e6a31f72368980a81a \ - --hash=sha256:54049021bc626f53a5394c29e8c444f726ee5a14b6e89e0ad118315b1f90f5e3 \ - --hash=sha256:54203e10405c06f8b6020bd1e076ae0fe6c194adcee12a5a78af3ffa3c57025e \ - --hash=sha256:5539ec0d6a3a5c6799b661b7e79166ad1b7ae71ccb59a92fcb6b4ef89295bc94 \ - --hash=sha256:5903e2db3d202a00ad9f0ec35a122c005e85d90c9836ab4cda628f01edf425e2 \ - --hash=sha256:5977f701b3fff36367a11087f30ea73c212e686d41cd363c50c022d48b011d8d \ - --hash=sha256:5c7ff1028e3c9fc5123a865ce17df1cb6424d180c503b8517afbe89aa566e6be \ - --hash=sha256:6148c9ae97a3e8bff9a1fc9c757fa164116f86c100468339730e717590a3fb77 \ - --hash=sha256:6234bf416a38d687c3ab7f79934d7fb2a42117a5b9813aca07de0a5398489023 \ - --hash=sha256:6290fe12fe8cefa6ea3c1c5b969d32c010dfe191d4392ff9b599a3f473cbe722 \ - --hash=sha256:63dd5e5b1e43b8fb1e91b79b7ceba1feba588b317d1edff385084fcc7a0a4538 \ - --hash=sha256:67a3ec705534a614b68bbf1c70efa777a21c3da3895d1c44510a41f5a7ae0453 \ - --hash=sha256:6b335919ffbaf98df8ff3c74f7a6decb8775882632952fd1810a017e38f15aee \ - --hash=sha256:6dcfb50ee25b3b7a1222a9123be1f9f89e56e67636b561441f0b304e25aaef8f \ - --hash=sha256:6f6ec32162d293b82f8b63a16edc80769662fbd5ae6fbd4936d3206a2c2cc63b \ - --hash=sha256:6f742e1fa45c0ed522b00ede565e18f97e4cf8d1883a712ac42d0339dfb0cce7 \ - --hash=sha256:717d17347567ded1e273aa09918650dfd6fd06f461549204570c7973537d4123 \ - --hash=sha256:746ac3cc00b5baea424dacddea3ec2c2702f9590de27d837aa67004db1eebc6e \ - --hash=sha256:74a2eb058da44fa3a877a49e2095b591d4913308bb424c418b77beb160c55ce3 \ - --hash=sha256:74c80b2bc2c2adb7b3d1941b2b60701ee2af8296fc8aad8b8bc48bc25767266c \ - --hash=sha256:7520d92c0e8fbbe63f36f20a5762db349ff574ad38ad7bc7732558a650439845 \ - --hash=sha256:76093107c531517001114f0ebdb4f46858ce818590363e3e99a4a2280334454a \ - --hash=sha256:797613182ffaaca0b9ad5f3b3d3ce5d21242c768f75e66c750b8292bd97c9de3 \ - --hash=sha256:7bc30cceb710cf6a44e9617e43eebb6e3e43ad855a34da7b4b6a73537d8a6763 \ - --hash=sha256:7c65738ac5ae32b8feef699a4ed0dc91a0c8618b347781b7461458bbcaaac7eb \ - --hash=sha256:7f78cb080c86fbf765920e5f1ef35af3f24ec4314d6675d0a21eaf41f6f2679c \ - --hash=sha256:898ea1850656d7d61832ef06aa9846ab3ddb1621b74f46de78fbc5e1a586ba83 \ - --hash=sha256:8ac32a189081ae0a10ba18993f10f338ec94341f0d5df8fff348043962f3c6f8 \ - --hash=sha256:8af249343fafd5ad90366a16d230fc265cf1149f26075dc9fe93cfd7c7173942 \ - --hash=sha256:8e08abcfe752a454d2cb89ff0c08f2d1ecd057ae3e8cc6d84638de853530ebab \ - --hash=sha256:8ea0c64d1bcbf201b285c2246c51a0c035ba3bbd306640007bc5844a3b4658c1 \ - --hash=sha256:907ad36b6a65cff7d88d7aca0f77c650546ba850a4f92c92ecb83590d4613249 \ - --hash=sha256:90c06228a6c3a7c9f776fe4fc0b7ff647fffd3bed93779a6913c804ae00c1073 \ - --hash=sha256:92deb95469928cc41fd4b42a95d8012fa6df93f6b1c0a83af0ffbc4a5e218cde \ - --hash=sha256:98e968cdaba43e45c73c3f306fca418c8009a957733bac85937c9f9cf3f4de27 \ - --hash=sha256:9e587fcfce2bcf06526a43cb705bdee21ac089096f2e271d75de9c339db3100c \ - --hash=sha256:9eb9c2eea7278206b5c6c1441fdd9dc420c278ead3f3b2cc87f9b693698cc500 \ - --hash=sha256:a533ec132f05fd9a1d959e7f34184cd7d5e8511584848dab85faefbaac573069 \ - --hash=sha256:a5444dce2e6fba0a1dc2d58d026e674f25f21de178c6f844342629bcef019f2f \ - --hash=sha256:a598a5c5767e1369d8f5b08695cab1d8160040f796c4416af76fd773d229b3c9 \ - --hash=sha256:a7058af1f53209fdf07745579ced525d38d481650a989b7aa4a3b484b901cdab \ - --hash=sha256:b08149419994cdd4d5eecf7fd4bc5986b5a9380285bcd01ab4c0d6bfca47b79d \ - --hash=sha256:b252e8d5cd66184b570d0d010de742736e8a4fab22c58299772b0c5a466d4b21 \ - --hash=sha256:b3d525648fe7c8b4977e460c18098f9f81d7991d72edfdc2f13cf96068f279bc \ - --hash=sha256:b3f00bb9403728b08eb3951e982ca0a409c7a871d709684623daeab79465b181 \ - --hash=sha256:ba5cf98b5dcb9bddd857da6713a503fa6d341043258ca823f0f5ab7ab4a94ee8 \ - --hash=sha256:bcf0c9902085976edc0232b75006ef38f89686901249ce14226b6877f88464fb \ - --hash=sha256:bda8f16ea99d6a6705e5946732e48487a448be874e54a4f73d514660ff7c05d3 \ - --hash=sha256:c033f2bc964156030772d31cbf7e5defea181238ce1f87b9455b786de7d30145 \ - --hash=sha256:c0fd8f41b54b58636402eb493afd512c23580456f022c1ba2db0f810c959ed0d \ - --hash=sha256:c3295f98bfeed2e867cab588f2a146a9db37a85e3ae9062abf46ba062bd29165 \ - --hash=sha256:c344c47e85678e410b064fc2ace14db86bb69db7ed5520c234bf13aed603ec30 \ - --hash=sha256:c555db4bc7a264bead5a7d63d92d41a1122fcd39cc62a4db815f45ad46f9c2c8 \ - --hash=sha256:c606aa5656dab6552e52ca368e43869c916338346bfaf6304e15c58fb113ea30 \ - --hash=sha256:c97989ae40a9746650fa196894f317dafc12227c808c774929dda0ff873a5954 \ - --hash=sha256:ca114790c9144c335d538852612d3e43ea0f075288f4849cf4b05d6cd2238ce7 \ - --hash=sha256:cb15595eb52870f84248d7cc97013a76f52ab02ff74d394be093b1d9b8b82bc0 \ - --hash=sha256:cb19177205d93b881f3f89e6081593676043a6828f59c78c17a0fd6c1fbed2ba \ - --hash=sha256:ce7320a945aac4bf0bb8901600e4f9409eb602f25ce3ef4d275b48f6d704a862 \ - --hash=sha256:d2710ae1e1b81d0f187883b6e9d66cecf8794b50e91aa1e73fc78bfb5503b5d9 \ - --hash=sha256:d36fc1709110ec1e87a229b201dd3ddc32aa01e98e7868083a794609b081c349 \ - --hash=sha256:d6630ec917e85c5356b2295744c8a97d40f007f96a1c76bf1928dc2e27465393 \ - --hash=sha256:d738ebab9f71ee652d9dbd0211057690022201b11197f9a7324fd4dba128aa97 \ - --hash=sha256:d85965d3ba21ee4999e83e992fecb86c4614d6920e40705501c0a1f80a583c12 \ - --hash=sha256:d904084985ca66459e93797e5e05985c048a9c0633655331144c089943e53d12 \ - --hash=sha256:d97a6d09c66087890c2ab5d49069e1e570583f7ac0314ecf98294c1b6aaebd38 \ - --hash=sha256:d99a9d168ebaffb74f36d011750e490085ac418f4db926cce3989c8fe6cb6b1b \ - --hash=sha256:dae86be9811493f9990ef44fff1685f5c1a3192e9061a71a109d527944eed551 \ - --hash=sha256:e0a2c961fc92abeff61d6444f2ce6ad35bb982db9fc8ff8a47455beacf454a57 \ - --hash=sha256:e56423766399b4c77b965f6aaab6c9546617b8994a956821cc507d00b91d978c \ - --hash=sha256:ea2e071661ba9cfe11eabbc81ac5376eaeb3061f6e72ec4cc86d7cdd1ffbdbbb \ - --hash=sha256:eb10ce8c03850e77f4d9518961c227be569e12f71525a7e90d17bca04299921d \ - --hash=sha256:ec75fc18cb9f4aca51c2cbace20cf6716e36850f44189644d2d69a875d5e0532 \ - --hash=sha256:ee62d4471ce86b108b19c3364db4b91180d13fe3510144872d6bad5401957360 \ - --hash=sha256:f062c45de8a1098cb137a1898819796a2491aec4e637a06b03f149315dff4d8f \ - --hash=sha256:f989ac8bc5595ff761a5ccd32bdb0768a117f36dd1504b1c2c074ed5d3f4df9c \ - --hash=sha256:fc432f6a2c4f720180959bc19aa37259651c1a4ed8af8afc84dd41c60f15f791 +aiohttp==3.13.2 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:04c3971421576ed24c191f610052bcb2f059e395bc2489dd99e397f9bc466329 \ + --hash=sha256:05c4dd3c48fb5f15db31f57eb35374cb0c09afdde532e7fb70a75aede0ed30f6 \ + --hash=sha256:070599407f4954021509193404c4ac53153525a19531051661440644728ba9a7 \ + --hash=sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254 \ + --hash=sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742 \ + --hash=sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad \ + --hash=sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906 \ + --hash=sha256:0e87dff73f46e969af38ab3f7cb75316a7c944e2e574ff7c933bc01b10def7f5 \ + --hash=sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811 \ + --hash=sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded \ + --hash=sha256:1f9b2c2d4b9d958b1f9ae0c984ec1dd6b6689e15c75045be8ccb4011426268ca \ + --hash=sha256:204ffff2426c25dfda401ba08da85f9c59525cdc42bda26660463dd1cbcfec6f \ + --hash=sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98 \ + --hash=sha256:20db2d67985d71ca033443a1ba2001c4b5693fe09b0e29f6d9358a99d4d62a8a \ + --hash=sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a \ + --hash=sha256:2372b15a5f62ed37789a6b383ff7344fc5b9f243999b0cd9b629d8bc5f5b4155 \ + --hash=sha256:23ad365e30108c422d0b4428cf271156dd56790f6dd50d770b8e360e6c5ab2e6 \ + --hash=sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e \ + --hash=sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b \ + --hash=sha256:27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e \ + --hash=sha256:29562998ec66f988d49fb83c9b01694fa927186b781463f376c5845c121e4e0b \ + --hash=sha256:2adebd4577724dcae085665f294cc57c8701ddd4d26140504db622b8d566d7aa \ + --hash=sha256:2ca6ffef405fc9c09a746cb5d019c1672cd7f402542e379afc66b370833170cf \ + --hash=sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5 \ + --hash=sha256:364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc \ + --hash=sha256:364f55663085d658b8462a1c3f17b2b84a5c2e1ba858e1b79bff7b2e24ad1514 \ + --hash=sha256:39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f \ + --hash=sha256:3a92cf4b9bea33e15ecbaa5c59921be0f23222608143d025c989924f7e3e0c07 \ + --hash=sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca \ + --hash=sha256:4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e \ + --hash=sha256:43dff14e35aba17e3d6d5ba628858fb8cb51e30f44724a2d2f0c75be492c55e9 \ + --hash=sha256:4647d02df098f6434bafd7f32ad14942f05a9caa06c7016fdcc816f343997dd0 \ + --hash=sha256:47f438b1a28e926c37632bff3c44df7d27c9b57aaf4e34b1def3c07111fdb782 \ + --hash=sha256:4dd3db9d0f4ebca1d887d76f7cdbcd1116ac0d05a9221b9dad82c64a62578c4d \ + --hash=sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213 \ + --hash=sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293 \ + --hash=sha256:53b07472f235eb80e826ad038c9d106c2f653584753f3ddab907c83f49eedead \ + --hash=sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04 \ + --hash=sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61 \ + --hash=sha256:585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3 \ + --hash=sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4 \ + --hash=sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b \ + --hash=sha256:6315fb6977f1d0dd41a107c527fee2ed5ab0550b7d885bc15fee20ccb17891da \ + --hash=sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6 \ + --hash=sha256:6c00dbcf5f0d88796151e264a8eab23de2997c9303dd7c0bf622e23b24d3ce22 \ + --hash=sha256:6e7352512f763f760baaed2637055c49134fd1d35b37c2dedfac35bfe5cf8725 \ + --hash=sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be \ + --hash=sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661 \ + --hash=sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e \ + --hash=sha256:7c3a50345635a02db61792c85bb86daffac05330f6473d524f1a4e3ef9d0046d \ + --hash=sha256:7fbdf5ad6084f1940ce88933de34b62358d0f4a0b6ec097362dcd3e5a65a4989 \ + --hash=sha256:7fd19df530c292542636c2a9a85854fab93474396a52f1695e799186bbd7f24c \ + --hash=sha256:868e195e39b24aaa930b063c08bb0c17924899c16c672a28a65afded9c46c6ec \ + --hash=sha256:8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45 \ + --hash=sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a \ + --hash=sha256:8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8 \ + --hash=sha256:8b2f1414f6a1e0683f212ec80e813f4abef94c739fd090b66c9adf9d2a05feac \ + --hash=sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694 \ + --hash=sha256:939ced4a7add92296b0ad38892ce62b98c619288a081170695c6babe4f50e636 \ + --hash=sha256:9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169 \ + --hash=sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a \ + --hash=sha256:960c2fc686ba27b535f9fd2b52d87ecd7e4fd1cf877f6a5cba8afb5b4a8bd204 \ + --hash=sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a \ + --hash=sha256:97a0895a8e840ab3520e2288db7cace3a1981300d48babeb50e7425609e2e0ab \ + --hash=sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae \ + --hash=sha256:99c5280a329d5fa18ef30fd10c793a190d996567667908bef8a7f81f8202b948 \ + --hash=sha256:9acda8604a57bb60544e4646a4615c1866ee6c04a8edef9b8ee6fd1d8fa2ddc8 \ + --hash=sha256:9c705601e16c03466cb72011bd1af55d68fa65b045356d8f96c216e5f6db0fa5 \ + --hash=sha256:9e8f8afb552297aca127c90cb840e9a1d4bfd6a10d7d8f2d9176e1acc69bad30 \ + --hash=sha256:9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16 \ + --hash=sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693 \ + --hash=sha256:9f377d0a924e5cc94dc620bc6366fc3e889586a7f18b748901cf016c916e2084 \ + --hash=sha256:a09a6d073fb5789456545bdee2474d14395792faa0527887f2f4ec1a486a59d3 \ + --hash=sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4 \ + --hash=sha256:a3b6fb0c207cc661fa0bf8c66d8d9b657331ccc814f4719468af61034b478592 \ + --hash=sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49 \ + --hash=sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a \ + --hash=sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e \ + --hash=sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4 \ + --hash=sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9 \ + --hash=sha256:b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b \ + --hash=sha256:b395bbca716c38bef3c764f187860e88c724b342c26275bc03e906142fc5964f \ + --hash=sha256:b59d13c443f8e049d9e94099c7e412e34610f1f49be0f230ec656a10692a5802 \ + --hash=sha256:ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb \ + --hash=sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b \ + --hash=sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011 \ + --hash=sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3 \ + --hash=sha256:c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7 \ + --hash=sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c \ + --hash=sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734 \ + --hash=sha256:d7bc4b7f9c4921eba72677cd9fedd2308f4a4ca3e12fab58935295ad9ea98700 \ + --hash=sha256:d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d \ + --hash=sha256:dacd50501cd017f8cccb328da0c90823511d70d24a323196826d923aad865901 \ + --hash=sha256:e036a3a645fe92309ec34b918394bb377950cbb43039a97edae6c08db64b23e2 \ + --hash=sha256:e09a0a06348a2dd73e7213353c90d709502d9786219f69b731f6caa0efeb46f5 \ + --hash=sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23 \ + --hash=sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8 \ + --hash=sha256:e2a9ea08e8c58bb17655630198833109227dea914cd20be660f52215f6de5613 \ + --hash=sha256:e3403f24bcb9c3b29113611c3c16a2a447c3953ecf86b79775e7be06f7ae7ccb \ + --hash=sha256:e574a7d61cf10351d734bcddabbe15ede0eaa8a02070d85446875dc11189a251 \ + --hash=sha256:e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6 \ + --hash=sha256:e736c93e9c274fce6419af4aac199984d866e55f8a4cec9114671d0ea9688780 \ + --hash=sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd \ + --hash=sha256:e7f8659a48995edee7229522984bd1009c1213929c769c2daa80b40fe49a180c \ + --hash=sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf \ + --hash=sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa \ + --hash=sha256:ed2f9c7216e53c3df02264f25d824b079cc5914f9e2deba94155190ef648ee40 \ + --hash=sha256:eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7 \ + --hash=sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673 \ + --hash=sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb \ + --hash=sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61 \ + --hash=sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940 \ + --hash=sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be \ + --hash=sha256:fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b \ + --hash=sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4 \ + --hash=sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476 \ + --hash=sha256:fed38a5edb7945f4d1bcabe2fcd05db4f6ec7e0e82560088b754f7e08d93772d \ + --hash=sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f \ + --hash=sha256:ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248 \ + --hash=sha256:ff5e771f5dcbc81c64898c597a434f7682f2259e0cd666932a913d53d1341d1a aiosignal==1.4.0 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e \ --hash=sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7 @@ -388,56 +388,38 @@ crispy-bootstrap4==2024.10 ; python_version >= "3.10" and python_version < "4.0" crudini==0.9.5 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:59ae650f45af82a64afc33eb876909ee0c4888dc4e8711ef59731c1edfda5e24 \ --hash=sha256:84bc208dc7d89571bdc3c99274259d0b32d6b3a692d4255524f2eb4b64e9195c -cryptography==46.0.7 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65 \ - --hash=sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832 \ - --hash=sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067 \ - --hash=sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de \ - --hash=sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4 \ - --hash=sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0 \ - --hash=sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b \ - --hash=sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968 \ - --hash=sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef \ - --hash=sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b \ - --hash=sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4 \ - --hash=sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3 \ - --hash=sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308 \ - --hash=sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e \ - --hash=sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163 \ - --hash=sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f \ - --hash=sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee \ - --hash=sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77 \ - --hash=sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85 \ - --hash=sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99 \ - --hash=sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7 \ - --hash=sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83 \ - --hash=sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85 \ - --hash=sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006 \ - --hash=sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb \ - --hash=sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e \ - --hash=sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba \ - --hash=sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325 \ - --hash=sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d \ - --hash=sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1 \ - --hash=sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1 \ - --hash=sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2 \ - --hash=sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0 \ - --hash=sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455 \ - --hash=sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842 \ - --hash=sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457 \ - --hash=sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15 \ - --hash=sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2 \ - --hash=sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c \ - --hash=sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb \ - --hash=sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5 \ - --hash=sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4 \ - --hash=sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902 \ - --hash=sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246 \ - --hash=sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022 \ - --hash=sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f \ - --hash=sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e \ - --hash=sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298 \ - --hash=sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce +cryptography==44.0.1 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:00918d859aa4e57db8299607086f793fa7813ae2ff5a4637e318a25ef82730f7 \ + --hash=sha256:1e8d181e90a777b63f3f0caa836844a1182f1f265687fac2115fcf245f5fbec3 \ + --hash=sha256:1f9a92144fa0c877117e9748c74501bea842f93d21ee00b0cf922846d9d0b183 \ + --hash=sha256:21377472ca4ada2906bc313168c9dc7b1d7ca417b63c1c3011d0c74b7de9ae69 \ + --hash=sha256:24979e9f2040c953a94bf3c6782e67795a4c260734e5264dceea65c8f4bae64a \ + --hash=sha256:2a46a89ad3e6176223b632056f321bc7de36b9f9b93b2cc1cccf935a3849dc62 \ + --hash=sha256:322eb03ecc62784536bc173f1483e76747aafeb69c8728df48537eb431cd1911 \ + --hash=sha256:436df4f203482f41aad60ed1813811ac4ab102765ecae7a2bbb1dbb66dcff5a7 \ + --hash=sha256:4f422e8c6a28cf8b7f883eb790695d6d45b0c385a2583073f3cec434cc705e1a \ + --hash=sha256:53f23339864b617a3dfc2b0ac8d5c432625c80014c25caac9082314e9de56f41 \ + --hash=sha256:5fed5cd6102bb4eb843e3315d2bf25fede494509bddadb81e03a859c1bc17b83 \ + --hash=sha256:610a83540765a8d8ce0f351ce42e26e53e1f774a6efb71eb1b41eb01d01c3d12 \ + --hash=sha256:6c8acf6f3d1f47acb2248ec3ea261171a671f3d9428e34ad0357148d492c7864 \ + --hash=sha256:6f76fdd6fd048576a04c5210d53aa04ca34d2ed63336d4abd306d0cbe298fddf \ + --hash=sha256:72198e2b5925155497a5a3e8c216c7fb3e64c16ccee11f0e7da272fa93b35c4c \ + --hash=sha256:887143b9ff6bad2b7570da75a7fe8bbf5f65276365ac259a5d2d5147a73775f2 \ + --hash=sha256:888fcc3fce0c888785a4876ca55f9f43787f4c5c1cc1e2e0da71ad481ff82c5b \ + --hash=sha256:8e6a85a93d0642bd774460a86513c5d9d80b5c002ca9693e63f6e540f1815ed0 \ + --hash=sha256:94f99f2b943b354a5b6307d7e8d19f5c423a794462bde2bf310c770ba052b1c4 \ + --hash=sha256:9b336599e2cb77b1008cb2ac264b290803ec5e8e89d618a5e978ff5eb6f715d9 \ + --hash=sha256:a2d8a7045e1ab9b9f803f0d9531ead85f90c5f2859e653b61497228b18452008 \ + --hash=sha256:b8272f257cf1cbd3f2e120f14c68bff2b6bdfcc157fafdee84a1b795efd72862 \ + --hash=sha256:bf688f615c29bfe9dfc44312ca470989279f0e94bb9f631f85e3459af8efc009 \ + --hash=sha256:d9c5b9f698a83c8bd71e0f4d3f9f839ef244798e5ffe96febfa9714717db7af7 \ + --hash=sha256:dd7c7e2d71d908dc0f8d2027e1604102140d84b155e658c20e8ad1304317691f \ + --hash=sha256:df978682c1504fc93b3209de21aeabf2375cb1571d4e61907b3e7a2540e83026 \ + --hash=sha256:e403f7f766ded778ecdb790da786b418a9f2394f36e8cc8b796cc056ab05f44f \ + --hash=sha256:eb3889330f2a4a148abead555399ec9a32b13b7c8ba969b72d8e500eb7ef84cd \ + --hash=sha256:f4daefc971c2d1f82f03097dc6f216744a6cd2ac0f04c68fb935ea2ba2a0d420 \ + --hash=sha256:f51f5705ab27898afda1aaa430f34ad90dc117421057782022edf0600bec5f14 \ + --hash=sha256:fd0ee90072861e276b0ff08bd627abec29e32a53b2be44e41dbcdf87cbee2b00 cxxfilt==0.3.0 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:774e85a8d0157775ed43276d89397d924b104135762d86b3a95f81f203094e07 \ --hash=sha256:7df6464ba5e8efbf0d8974c0b2c78b32546676f06059a83515dbdfa559b34214 @@ -534,9 +516,9 @@ django-recaptcha==4.0.0 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:5316438f97700c431d65351470d1255047e3f2cd9af0f2f13592b637dad9213e django-settings-export==1.2.1 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:fceeae49fc597f654c1217415d8e049fc81c930b7154f5d8f28c432db738ff79 -django==5.1.15 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:117871e58d6eda37f09870b7d73a3d66567b03aecd515b386b1751177c413432 \ - --hash=sha256:46a356b5ff867bece73fc6365e081f21c569973403ee7e9b9a0316f27d0eb947 +django==5.1.14 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:2a4b9c20404fd1bf50aaaa5542a19d860594cba1354f688f642feb271b91df27 \ + --hash=sha256:b98409fb31fdd6e8c3a6ba2eef3415cc5c0020057b43b21ba7af6eff5f014831 djangorestframework==3.15.2 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:2b8871b062ba1aefc2de01f773875441a961fefbf79f5eed1e32b2f096944b20 \ --hash=sha256:36fe88cd2d6c6bec23dca9804bab2ba5517a8bb9d8f47ebc68981b56840107ad @@ -890,9 +872,9 @@ ida-settings==3.3.0 ; python_version >= "3.10" and python_version < "4.0" \ idapro==0.0.7 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:0b2e184b19e4d8404ea48b1f17f39b21b054931315940ee9716ef8559144988f \ --hash=sha256:3cb8d48ac21a19e6a0ef65eacd0005a1f9c40a264a7bcb675914eaa20c6e1371 -idna==3.15 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8 \ - --hash=sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc +idna==3.10 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 incremental==24.7.2 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:8cb2c3431530bec48ad70513931a760f446ad6c25e8333ca5d95e24b0ed7b8fe \ --hash=sha256:fb4f1d47ee60efe87d4f6f0ebb5f70b9760db2b2574c59c8e8912be4ebd464c9 @@ -910,147 +892,151 @@ jsbeautifier==1.15.1 ; python_version >= "3.10" and python_version < "4.0" \ lnkparse3==1.5.0 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:3ecbd8f4107be07b8e8d7b770daa53271abf66222ee892618d30f86952e1121a \ --hash=sha256:56b549389254f4d25375621249aa3a8c31f1dabf375e88bf7dc8c73a0f4f8f1e -lxml==6.1.0 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2 \ - --hash=sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773 \ - --hash=sha256:045e387d1f4f42a418380930fa3f45c73c9b392faf67e495e58902e68e8f44a7 \ - --hash=sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad \ - --hash=sha256:07f98f5496f96bf724b1e3c933c107f0cbf2745db18c03d2e13a291c3afd2635 \ - --hash=sha256:08950a23f296b3f83521577274e3d3b0f3d739bf2e68d01a752e4288bc50d286 \ - --hash=sha256:0d082495c5fcf426e425a6e28daaba1fcb6d8f854a4ff01effb1f1f381203eb9 \ - --hash=sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54 \ - --hash=sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c \ - --hash=sha256:11a873c77a181b4fef9c2e357d08ed399542c2af1390101da66720a19c7c9618 \ - --hash=sha256:183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7 \ - --hash=sha256:19f4164243fc206d12ed3d866e80e74f5bc3627966520da1a5f97e42c32a3f39 \ - --hash=sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69 \ - --hash=sha256:1bc4cc83fb7f66ffb16f74d6dd0162e144333fc36ebcce32246f80c8735b2551 \ - --hash=sha256:1dd6a1c3ad4cb674f44525d9957f3e9c209bb6dd9213245195167a281fcc2bdc \ - --hash=sha256:20cf4d0651987c906a2f5cba4e3a8d6ba4bfdf973cfe2a96c0d6053888ea2ecd \ - --hash=sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd \ - --hash=sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405 \ - --hash=sha256:23a5dc68e08ed13331d61815c08f260f46b4a60fdd1640bbeb82cf89a9d90289 \ - --hash=sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b \ - --hash=sha256:2593a0a6621545b9095b71ad74ed4226eba438a7d9fc3712a99bdb15508cf93a \ - --hash=sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d \ - --hash=sha256:26c5272c6a4bf4cf32d3f5a7890c942b0e04438691157d341616d02cca74d4bd \ - --hash=sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac \ - --hash=sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62 \ - --hash=sha256:29f5c00cb7d752bce2c70ebd2d31b0a42f9499ffdd3ecb2f31a5b73ee43031ad \ - --hash=sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f \ - --hash=sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad \ - --hash=sha256:363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c \ - --hash=sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292 \ - --hash=sha256:37448bf9c7d7adfc5254763901e2bbd6bb876228dfc1fc7f66e58c06368a7544 \ - --hash=sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491 \ - --hash=sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120 \ - --hash=sha256:3ae5d8d5427f3cc317e7950f2da7ad276df0cfa37b8de2f5658959e618ea8512 \ - --hash=sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb \ - --hash=sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32 \ - --hash=sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2 \ - --hash=sha256:41dcc4c7b10484257cbd6c37b83ddb26df2b0e5aff5ac00d095689015af868ec \ - --hash=sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88 \ - --hash=sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43 \ - --hash=sha256:4642e04449a1e164b5ff71ffd901ddb772dfabf5c9adf1b7be5dffe1212bc037 \ - --hash=sha256:468479e52ecf3ec23799c863336d02c05fc2f7ffd1a1424eeeb9a28d4eb69d13 \ - --hash=sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03 \ - --hash=sha256:481d6e2104285d9add34f41b42b247b76b61c5b5c26c303c2e9707bbf8bd9a64 \ - --hash=sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485 \ - --hash=sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16 \ - --hash=sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb \ - --hash=sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9 \ - --hash=sha256:4e2c54d6b47361d0f1d3bc8d4e082ad87201e56ccdcca4d3b9ee3644ff595ec8 \ - --hash=sha256:52b0ac6903cf74ebf997eb8c682d2fbac7d1ab7e4c552413eec55868a9b73f39 \ - --hash=sha256:546b66c0dd1bb8d9fa89d7123e5fa19a8aff3a1f2141eb22df96112afb17b842 \ - --hash=sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f \ - --hash=sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105 \ - --hash=sha256:5cfa1a34df366d9dc0d5eaf420f4cf2bb1e1bebe1066d1c2fc28c179f8a4004c \ - --hash=sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7 \ - --hash=sha256:6262b87f9e5c1e5fe501d6c153247289af42eb44ad7660b9b3de17baaf92d6f6 \ - --hash=sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5 \ - --hash=sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d \ - --hash=sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9 \ - --hash=sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d \ - --hash=sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb \ - --hash=sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f \ - --hash=sha256:76b958b4ea3104483c20f74866d55aa056546e15ebe83dd7aecd63698f43b755 \ - --hash=sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb \ - --hash=sha256:7ba11752e346bd804ea312ec2eea2532dfa8b8d3261d81a32ef9e6ab16256280 \ - --hash=sha256:7da13bb6fbadfafb474e0226a30570a3445cfd47c86296f2446dafbd77079ace \ - --hash=sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e \ - --hash=sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33 \ - --hash=sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5 \ - --hash=sha256:81ff55c70b67d19d52b6fd118a114c0a4c97d799cd3089ff9bd9e2ff4b414ee2 \ - --hash=sha256:857efde87d365706590847b916baff69c0bc9252dc5af030e378c9800c0b10e3 \ - --hash=sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585 \ - --hash=sha256:8c11b984b5ce6add4dccc7144c7be5d364d298f15b0c6a57da1991baedc750ce \ - --hash=sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946 \ - --hash=sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e \ - --hash=sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a \ - --hash=sha256:920354904d1cb86577d4b3cfe2830c2dbe81d6f4449e57ada428f1609b5985f7 \ - --hash=sha256:942454ff253da14218f972b23dc72fa4edf6c943f37edd19cd697618b626fac5 \ - --hash=sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28 \ - --hash=sha256:976a6b39b1b13e8c354ad8d3f261f3a4ac6609518af91bdb5094760a08f132c4 \ - --hash=sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c \ - --hash=sha256:9c03e048b6ce8e77b09c734e931584894ecd58d08296804ca2d0b184c933ce50 \ - --hash=sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9 \ - --hash=sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495 \ - --hash=sha256:9f93d5b8b07f73e8c77e3c6556a3db269918390c804b5e5fcdd4858232cc8f16 \ - --hash=sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45 \ - --hash=sha256:a02ca8fe48815bddcfca3248efe54451abb9dbf2f7d1c5744c8aa4142d476919 \ - --hash=sha256:a1d9b99e5b2597e4f5aed2484fef835256fa1b68a19e4265c97628ef4bf8bcf4 \ - --hash=sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc \ - --hash=sha256:a31286dbb5e74c8e9a5344465b77ab4c5bd511a253b355b5ca2fae7e579fafec \ - --hash=sha256:a86f06f059e22a0d574990ee2df24ede03f7f3c68c1336293eee9536c4c776cd \ - --hash=sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8 \ - --hash=sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f \ - --hash=sha256:b6c2f225662bc5ad416bdd06f72ca301b31b39ce4261f0e0097017fc2891b940 \ - --hash=sha256:bb40648d96157f9081886defe13eac99253e663be969ff938a9289eff6e47b72 \ - --hash=sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366 \ - --hash=sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814 \ - --hash=sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690 \ - --hash=sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13 \ - --hash=sha256:c08da09dc003c9e8c70e06b53a11db6fb3b250c21c4236b03c7d7b443c318e7a \ - --hash=sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819 \ - --hash=sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c \ - --hash=sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86 \ - --hash=sha256:c53fa3a5a52122d590e847a57ccf955557b9634a7f99ff5a35131321b0a85317 \ - --hash=sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180 \ - --hash=sha256:c748ebcb6877de89f48ab90ca96642ac458fff5dec291a2b9337cd4d0934e383 \ - --hash=sha256:c871299c595ee004d186f61840f0bfc4941aa3f17c8ba4a565ead7e4f4f820ee \ - --hash=sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe \ - --hash=sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181 \ - --hash=sha256:cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9 \ - --hash=sha256:d036ee7b99d5148072ac7c9b847193decdfeac633db350363f7bce4fff108f0e \ - --hash=sha256:d0d799ff958655781296ec870d5e2448e75150da2b3d07f13ff5b0c2c35beefd \ - --hash=sha256:d1392c569c032f78a11a25d1de1c43fff13294c793b39e19d84fade3045cbbc3 \ - --hash=sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d \ - --hash=sha256:d3829a6e6fd550a219564912d4002c537f65da4c6ae4e093cc34462f4fa027ad \ - --hash=sha256:d43aa26dcda363f21e79afa0668f5029ed7394b3bb8c92a6927a3d34e8b610ea \ - --hash=sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24 \ - --hash=sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d \ - --hash=sha256:db88156fcf544cdbf0d95588051515cfdfd4c876fc66444eb98bceb5d6db76de \ - --hash=sha256:de550d129f18d8ab819651ffe4f38b1b713c7e116707de3c0c6400d0ef34fbc1 \ - --hash=sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d \ - --hash=sha256:e3c4f84b24a1fcba435157d111c4b755099c6ff00a3daee1ad281817de75ed11 \ - --hash=sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9 \ - --hash=sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2 \ - --hash=sha256:e80807d72f96b96ad5588cb85c75616e4f2795a7737d4630784c51497beb7776 \ - --hash=sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f \ - --hash=sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93 \ - --hash=sha256:f15401d8d3dbf239e23c818afc10c7207f7b95f9a307e092122b6f86dd43209a \ - --hash=sha256:f504d861d9f2a8f94020130adac88d66de93841707a23a86244263d1e54682f5 \ - --hash=sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d \ - --hash=sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d \ - --hash=sha256:fcf3da95e93349e0647d48d4b36a12783105bcc74cb0c416952f9988410846a3 \ - --hash=sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086 \ - --hash=sha256:ffb34ea45a82dd637c2c97ae1bbb920850c1e59bcae79ce1c15af531d83e7215 +lxml==5.3.0 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:01220dca0d066d1349bd6a1726856a78f7929f3878f7e2ee83c296c69495309e \ + --hash=sha256:02ced472497b8362c8e902ade23e3300479f4f43e45f4105c85ef43b8db85229 \ + --hash=sha256:052d99051e77a4f3e8482c65014cf6372e61b0a6f4fe9edb98503bb5364cfee3 \ + --hash=sha256:07da23d7ee08577760f0a71d67a861019103e4812c87e2fab26b039054594cc5 \ + --hash=sha256:094cb601ba9f55296774c2d57ad68730daa0b13dc260e1f941b4d13678239e70 \ + --hash=sha256:0a7056921edbdd7560746f4221dca89bb7a3fe457d3d74267995253f46343f15 \ + --hash=sha256:0c120f43553ec759f8de1fee2f4794452b0946773299d44c36bfe18e83caf002 \ + --hash=sha256:0d7b36afa46c97875303a94e8f3ad932bf78bace9e18e603f2085b652422edcd \ + --hash=sha256:0fdf3a3059611f7585a78ee10399a15566356116a4288380921a4b598d807a22 \ + --hash=sha256:109fa6fede314cc50eed29e6e56c540075e63d922455346f11e4d7a036d2b8cf \ + --hash=sha256:146173654d79eb1fc97498b4280c1d3e1e5d58c398fa530905c9ea50ea849b22 \ + --hash=sha256:1473427aff3d66a3fa2199004c3e601e6c4500ab86696edffdbc84954c72d832 \ + --hash=sha256:1483fd3358963cc5c1c9b122c80606a3a79ee0875bcac0204149fa09d6ff2727 \ + --hash=sha256:168f2dfcfdedf611eb285efac1516c8454c8c99caf271dccda8943576b67552e \ + --hash=sha256:17e8d968d04a37c50ad9c456a286b525d78c4a1c15dd53aa46c1d8e06bf6fa30 \ + --hash=sha256:18feb4b93302091b1541221196a2155aa296c363fd233814fa11e181adebc52f \ + --hash=sha256:1afe0a8c353746e610bd9031a630a95bcfb1a720684c3f2b36c4710a0a96528f \ + --hash=sha256:1d04f064bebdfef9240478f7a779e8c5dc32b8b7b0b2fc6a62e39b928d428e51 \ + --hash=sha256:1fdc9fae8dd4c763e8a31e7630afef517eab9f5d5d31a278df087f307bf601f4 \ + --hash=sha256:1ffc23010330c2ab67fac02781df60998ca8fe759e8efde6f8b756a20599c5de \ + --hash=sha256:20094fc3f21ea0a8669dc4c61ed7fa8263bd37d97d93b90f28fc613371e7a875 \ + --hash=sha256:213261f168c5e1d9b7535a67e68b1f59f92398dd17a56d934550837143f79c42 \ + --hash=sha256:218c1b2e17a710e363855594230f44060e2025b05c80d1f0661258142b2add2e \ + --hash=sha256:23e0553b8055600b3bf4a00b255ec5c92e1e4aebf8c2c09334f8368e8bd174d6 \ + --hash=sha256:25f1b69d41656b05885aa185f5fdf822cb01a586d1b32739633679699f220391 \ + --hash=sha256:2b3778cb38212f52fac9fe913017deea2fdf4eb1a4f8e4cfc6b009a13a6d3fcc \ + --hash=sha256:2bc9fd5ca4729af796f9f59cd8ff160fe06a474da40aca03fcc79655ddee1a8b \ + --hash=sha256:2c226a06ecb8cdef28845ae976da407917542c5e6e75dcac7cc33eb04aaeb237 \ + --hash=sha256:2c3406b63232fc7e9b8783ab0b765d7c59e7c59ff96759d8ef9632fca27c7ee4 \ + --hash=sha256:2c86bf781b12ba417f64f3422cfc302523ac9cd1d8ae8c0f92a1c66e56ef2e86 \ + --hash=sha256:2d9b8d9177afaef80c53c0a9e30fa252ff3036fb1c6494d427c066a4ce6a282f \ + --hash=sha256:2dec2d1130a9cda5b904696cec33b2cfb451304ba9081eeda7f90f724097300a \ + --hash=sha256:2dfab5fa6a28a0b60a20638dc48e6343c02ea9933e3279ccb132f555a62323d8 \ + --hash=sha256:2ecdd78ab768f844c7a1d4a03595038c166b609f6395e25af9b0f3f26ae1230f \ + --hash=sha256:315f9542011b2c4e1d280e4a20ddcca1761993dda3afc7a73b01235f8641e903 \ + --hash=sha256:36aef61a1678cb778097b4a6eeae96a69875d51d1e8f4d4b491ab3cfb54b5a03 \ + --hash=sha256:384aacddf2e5813a36495233b64cb96b1949da72bef933918ba5c84e06af8f0e \ + --hash=sha256:3879cc6ce938ff4eb4900d901ed63555c778731a96365e53fadb36437a131a99 \ + --hash=sha256:3c174dc350d3ec52deb77f2faf05c439331d6ed5e702fc247ccb4e6b62d884b7 \ + --hash=sha256:3eb44520c4724c2e1a57c0af33a379eee41792595023f367ba3952a2d96c2aab \ + --hash=sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d \ + --hash=sha256:41ce1f1e2c7755abfc7e759dc34d7d05fd221723ff822947132dc934d122fe22 \ + --hash=sha256:423b121f7e6fa514ba0c7918e56955a1d4470ed35faa03e3d9f0e3baa4c7e492 \ + --hash=sha256:44264ecae91b30e5633013fb66f6ddd05c006d3e0e884f75ce0b4755b3e3847b \ + --hash=sha256:482c2f67761868f0108b1743098640fbb2a28a8e15bf3f47ada9fa59d9fe08c3 \ + --hash=sha256:4b0c7a688944891086ba192e21c5229dea54382f4836a209ff8d0a660fac06be \ + --hash=sha256:4c1fefd7e3d00921c44dc9ca80a775af49698bbfd92ea84498e56acffd4c5469 \ + --hash=sha256:4e109ca30d1edec1ac60cdbe341905dc3b8f55b16855e03a54aaf59e51ec8c6f \ + --hash=sha256:501d0d7e26b4d261fca8132854d845e4988097611ba2531408ec91cf3fd9d20a \ + --hash=sha256:516f491c834eb320d6c843156440fe7fc0d50b33e44387fcec5b02f0bc118a4c \ + --hash=sha256:51806cfe0279e06ed8500ce19479d757db42a30fd509940b1701be9c86a5ff9a \ + --hash=sha256:562e7494778a69086f0312ec9689f6b6ac1c6b65670ed7d0267e49f57ffa08c4 \ + --hash=sha256:56b9861a71575f5795bde89256e7467ece3d339c9b43141dbdd54544566b3b94 \ + --hash=sha256:5b8f5db71b28b8c404956ddf79575ea77aa8b1538e8b2ef9ec877945b3f46442 \ + --hash=sha256:5c2fb570d7823c2bbaf8b419ba6e5662137f8166e364a8b2b91051a1fb40ab8b \ + --hash=sha256:5c54afdcbb0182d06836cc3d1be921e540be3ebdf8b8a51ee3ef987537455f84 \ + --hash=sha256:5d6a6972b93c426ace71e0be9a6f4b2cfae9b1baed2eed2006076a746692288c \ + --hash=sha256:609251a0ca4770e5a8768ff902aa02bf636339c5a93f9349b48eb1f606f7f3e9 \ + --hash=sha256:62d172f358f33a26d6b41b28c170c63886742f5b6772a42b59b4f0fa10526cb1 \ + --hash=sha256:62f7fdb0d1ed2065451f086519865b4c90aa19aed51081979ecd05a21eb4d1be \ + --hash=sha256:658f2aa69d31e09699705949b5fc4719cbecbd4a97f9656a232e7d6c7be1a367 \ + --hash=sha256:65ab5685d56914b9a2a34d67dd5488b83213d680b0c5d10b47f81da5a16b0b0e \ + --hash=sha256:68934b242c51eb02907c5b81d138cb977b2129a0a75a8f8b60b01cb8586c7b21 \ + --hash=sha256:68b87753c784d6acb8a25b05cb526c3406913c9d988d51f80adecc2b0775d6aa \ + --hash=sha256:69959bd3167b993e6e710b99051265654133a98f20cec1d9b493b931942e9c16 \ + --hash=sha256:6a7095eeec6f89111d03dabfe5883a1fd54da319c94e0fb104ee8f23616b572d \ + --hash=sha256:6b038cc86b285e4f9fea2ba5ee76e89f21ed1ea898e287dc277a25884f3a7dfe \ + --hash=sha256:6ba0d3dcac281aad8a0e5b14c7ed6f9fa89c8612b47939fc94f80b16e2e9bc83 \ + --hash=sha256:6e91cf736959057f7aac7adfc83481e03615a8e8dd5758aa1d95ea69e8931dba \ + --hash=sha256:6ee8c39582d2652dcd516d1b879451500f8db3fe3607ce45d7c5957ab2596040 \ + --hash=sha256:6f651ebd0b21ec65dfca93aa629610a0dbc13dbc13554f19b0113da2e61a4763 \ + --hash=sha256:71a8dd38fbd2f2319136d4ae855a7078c69c9a38ae06e0c17c73fd70fc6caad8 \ + --hash=sha256:74068c601baff6ff021c70f0935b0c7bc528baa8ea210c202e03757c68c5a4ff \ + --hash=sha256:7437237c6a66b7ca341e868cda48be24b8701862757426852c9b3186de1da8a2 \ + --hash=sha256:747a3d3e98e24597981ca0be0fd922aebd471fa99d0043a3842d00cdcad7ad6a \ + --hash=sha256:74bcb423462233bc5d6066e4e98b0264e7c1bed7541fff2f4e34fe6b21563c8b \ + --hash=sha256:78d9b952e07aed35fe2e1a7ad26e929595412db48535921c5013edc8aa4a35ce \ + --hash=sha256:7b1cd427cb0d5f7393c31b7496419da594fe600e6fdc4b105a54f82405e6626c \ + --hash=sha256:7d3d1ca42870cdb6d0d29939630dbe48fa511c203724820fc0fd507b2fb46577 \ + --hash=sha256:7e2f58095acc211eb9d8b5771bf04df9ff37d6b87618d1cbf85f92399c98dae8 \ + --hash=sha256:7f41026c1d64043a36fda21d64c5026762d53a77043e73e94b71f0521939cc71 \ + --hash=sha256:81b4e48da4c69313192d8c8d4311e5d818b8be1afe68ee20f6385d0e96fc9512 \ + --hash=sha256:86a6b24b19eaebc448dc56b87c4865527855145d851f9fc3891673ff97950540 \ + --hash=sha256:874a216bf6afaf97c263b56371434e47e2c652d215788396f60477540298218f \ + --hash=sha256:89e043f1d9d341c52bf2af6d02e6adde62e0a46e6755d5eb60dc6e4f0b8aeca2 \ + --hash=sha256:8c72e9563347c7395910de6a3100a4840a75a6f60e05af5e58566868d5eb2d6a \ + --hash=sha256:8dc2c0395bea8254d8daebc76dcf8eb3a95ec2a46fa6fae5eaccee366bfe02ce \ + --hash=sha256:8f0de2d390af441fe8b2c12626d103540b5d850d585b18fcada58d972b74a74e \ + --hash=sha256:92e67a0be1639c251d21e35fe74df6bcc40cba445c2cda7c4a967656733249e2 \ + --hash=sha256:94d6c3782907b5e40e21cadf94b13b0842ac421192f26b84c45f13f3c9d5dc27 \ + --hash=sha256:97acf1e1fd66ab53dacd2c35b319d7e548380c2e9e8c54525c6e76d21b1ae3b1 \ + --hash=sha256:9ada35dd21dc6c039259596b358caab6b13f4db4d4a7f8665764d616daf9cc1d \ + --hash=sha256:9c52100e2c2dbb0649b90467935c4b0de5528833c76a35ea1a2691ec9f1ee7a1 \ + --hash=sha256:9e41506fec7a7f9405b14aa2d5c8abbb4dbbd09d88f9496958b6d00cb4d45330 \ + --hash=sha256:9e4b47ac0f5e749cfc618efdf4726269441014ae1d5583e047b452a32e221920 \ + --hash=sha256:9fb81d2824dff4f2e297a276297e9031f46d2682cafc484f49de182aa5e5df99 \ + --hash=sha256:a0eabd0a81625049c5df745209dc7fcef6e2aea7793e5f003ba363610aa0a3ff \ + --hash=sha256:a3d819eb6f9b8677f57f9664265d0a10dd6551d227afb4af2b9cd7bdc2ccbf18 \ + --hash=sha256:a87de7dd873bf9a792bf1e58b1c3887b9264036629a5bf2d2e6579fe8e73edff \ + --hash=sha256:aa617107a410245b8660028a7483b68e7914304a6d4882b5ff3d2d3eb5948d8c \ + --hash=sha256:aac0bbd3e8dd2d9c45ceb82249e8bdd3ac99131a32b4d35c8af3cc9db1657179 \ + --hash=sha256:ab6dd83b970dc97c2d10bc71aa925b84788c7c05de30241b9e96f9b6d9ea3080 \ + --hash=sha256:ace2c2326a319a0bb8a8b0e5b570c764962e95818de9f259ce814ee666603f19 \ + --hash=sha256:ae5fe5c4b525aa82b8076c1a59d642c17b6e8739ecf852522c6321852178119d \ + --hash=sha256:b11a5d918a6216e521c715b02749240fb07ae5a1fefd4b7bf12f833bc8b4fe70 \ + --hash=sha256:b1c8c20847b9f34e98080da785bb2336ea982e7f913eed5809e5a3c872900f32 \ + --hash=sha256:b369d3db3c22ed14c75ccd5af429086f166a19627e84a8fdade3f8f31426e52a \ + --hash=sha256:b710bc2b8292966b23a6a0121f7a6c51d45d2347edcc75f016ac123b8054d3f2 \ + --hash=sha256:bd96517ef76c8654446fc3db9242d019a1bb5fe8b751ba414765d59f99210b79 \ + --hash=sha256:c00f323cc00576df6165cc9d21a4c21285fa6b9989c5c39830c3903dc4303ef3 \ + --hash=sha256:c162b216070f280fa7da844531169be0baf9ccb17263cf5a8bf876fcd3117fa5 \ + --hash=sha256:c1a69e58a6bb2de65902051d57fde951febad631a20a64572677a1052690482f \ + --hash=sha256:c1f794c02903c2824fccce5b20c339a1a14b114e83b306ff11b597c5f71a1c8d \ + --hash=sha256:c24037349665434f375645fa9d1f5304800cec574d0310f618490c871fd902b3 \ + --hash=sha256:c300306673aa0f3ed5ed9372b21867690a17dba38c68c44b287437c362ce486b \ + --hash=sha256:c56a1d43b2f9ee4786e4658c7903f05da35b923fb53c11025712562d5cc02753 \ + --hash=sha256:c6379f35350b655fd817cd0d6cbeef7f265f3ae5fedb1caae2eb442bbeae9ab9 \ + --hash=sha256:c802e1c2ed9f0c06a65bc4ed0189d000ada8049312cfeab6ca635e39c9608957 \ + --hash=sha256:cb83f8a875b3d9b458cada4f880fa498646874ba4011dc974e071a0a84a1b033 \ + --hash=sha256:cf120cce539453ae086eacc0130a324e7026113510efa83ab42ef3fcfccac7fb \ + --hash=sha256:dd36439be765e2dde7660212b5275641edbc813e7b24668831a5c8ac91180656 \ + --hash=sha256:dd5350b55f9fecddc51385463a4f67a5da829bc741e38cf689f38ec9023f54ab \ + --hash=sha256:df5c7333167b9674aa8ae1d4008fa4bc17a313cc490b2cca27838bbdcc6bb15b \ + --hash=sha256:e63601ad5cd8f860aa99d109889b5ac34de571c7ee902d6812d5d9ddcc77fa7d \ + --hash=sha256:e92ce66cd919d18d14b3856906a61d3f6b6a8500e0794142338da644260595cd \ + --hash=sha256:e99f5507401436fdcc85036a2e7dc2e28d962550afe1cbfc07c40e454256a859 \ + --hash=sha256:ea2e2f6f801696ad7de8aec061044d6c8c0dd4037608c7cab38a9a4d316bfb11 \ + --hash=sha256:eafa2c8658f4e560b098fe9fc54539f86528651f61849b22111a9b107d18910c \ + --hash=sha256:ecd4ad8453ac17bc7ba3868371bffb46f628161ad0eefbd0a855d2c8c32dd81a \ + --hash=sha256:ee70d08fd60c9565ba8190f41a46a54096afa0eeb8f76bd66f2c25d3b1b83005 \ + --hash=sha256:eec1bb8cdbba2925bedc887bc0609a80e599c75b12d87ae42ac23fd199445654 \ + --hash=sha256:ef0c1fe22171dd7c7c27147f2e9c3e86f8bdf473fed75f16b0c2e84a5030ce80 \ + --hash=sha256:f2901429da1e645ce548bf9171784c0f74f0718c3f6150ce166be39e4dd66c3e \ + --hash=sha256:f422a209d2455c56849442ae42f25dbaaba1c6c3f501d58761c619c7836642ec \ + --hash=sha256:f65e5120863c2b266dbcc927b306c5b78e502c71edf3295dfcb9501ec96e5fc7 \ + --hash=sha256:f7d4a670107d75dfe5ad080bed6c341d18c4442f9378c9f58e5851e86eb79965 \ + --hash=sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945 \ + --hash=sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8 maco==1.1.8 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:ab2d1d8e846c0abc455d16f718ba71dda5492ddc22533484156090aa4439fb06 \ --hash=sha256:e0985efdf645d3c55e3d4d4f2bf40b8d2260fa4add608bb8e8fdefba0500cb4a -mako==1.3.12 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9 \ - --hash=sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a +mako==1.3.8 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:42f48953c7eb91332040ff567eb7eea69b22e7a4affbc5ba8e845e8f730f6627 \ + --hash=sha256:577b97e414580d3e088d47c2dbbe9594aa7a5146ed2875d4dfa9075af2dd3cc8 markdown-it-py==3.0.0 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb @@ -1552,101 +1538,81 @@ peepdf-3==5.0.0 ; python_version >= "3.10" and python_version < "4.0" \ pefile==2024.8.26 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632 \ --hash=sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f -pillow==12.2.0 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9 \ - --hash=sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5 \ - --hash=sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987 \ - --hash=sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9 \ - --hash=sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b \ - --hash=sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f \ - --hash=sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd \ - --hash=sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e \ - --hash=sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e \ - --hash=sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe \ - --hash=sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795 \ - --hash=sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601 \ - --hash=sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1 \ - --hash=sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed \ - --hash=sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea \ - --hash=sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5 \ - --hash=sha256:2e589959f10d9824d39b350472b92f0ce3b443c0a3442ebf41c40cb8361c5b97 \ - --hash=sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453 \ - --hash=sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98 \ - --hash=sha256:34c0d99ecccea270c04882cb3b86e7b57296079c9a4aff88cb3b33563d95afaa \ - --hash=sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b \ - --hash=sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d \ - --hash=sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705 \ - --hash=sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8 \ - --hash=sha256:3e080565d8d7c671db5802eedfb438e5565ffa40115216eabb8cd52d0ecce024 \ - --hash=sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0 \ - --hash=sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286 \ - --hash=sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150 \ - --hash=sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2 \ - --hash=sha256:51c4167c34b0d8ba05b547a3bb23578d0ba17b80a5593f93bd8ecb123dd336a3 \ - --hash=sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b \ - --hash=sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f \ - --hash=sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463 \ - --hash=sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940 \ - --hash=sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166 \ - --hash=sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed \ - --hash=sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f \ - --hash=sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795 \ - --hash=sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780 \ - --hash=sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7 \ - --hash=sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1 \ - --hash=sha256:673aa32138f3e7531ccdbca7b3901dba9b70940a19ccecc6a37c77d5fdeb05b5 \ - --hash=sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295 \ - --hash=sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b \ - --hash=sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354 \ - --hash=sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60 \ - --hash=sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65 \ - --hash=sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005 \ - --hash=sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c \ - --hash=sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be \ - --hash=sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5 \ - --hash=sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06 \ - --hash=sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae \ - --hash=sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c \ - --hash=sha256:88d387ff40b3ff7c274947ed3125dedf5262ec6919d83946753b5f3d7c67ea4c \ - --hash=sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612 \ - --hash=sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e \ - --hash=sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab \ - --hash=sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808 \ - --hash=sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f \ - --hash=sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e \ - --hash=sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909 \ - --hash=sha256:975385f4776fafde056abb318f612ef6285b10a1f12b8570f3647ad0d74b48ec \ - --hash=sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe \ - --hash=sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50 \ - --hash=sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4 \ - --hash=sha256:a4e8f36e677d3336f35089648c8955c51c6d386a13cf6ee9c189c5f5bd713a9f \ - --hash=sha256:a52edc8bfff4429aaabdf4d9ee0daadbbf8562364f940937b941f87a4290f5ff \ - --hash=sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5 \ - --hash=sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb \ - --hash=sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414 \ - --hash=sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1 \ - --hash=sha256:b85f66ae9eb53e860a873b858b789217ba505e5e405a24b85c0464822fe88032 \ - --hash=sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76 \ - --hash=sha256:bd9c0c7a0c681a347b3194c500cb1e6ca9cab053ea4d82a5cf45b6b754560136 \ - --hash=sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e \ - --hash=sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c \ - --hash=sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3 \ - --hash=sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea \ - --hash=sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f \ - --hash=sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104 \ - --hash=sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176 \ - --hash=sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24 \ - --hash=sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3 \ - --hash=sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4 \ - --hash=sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed \ - --hash=sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43 \ - --hash=sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421 \ - --hash=sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7 \ - --hash=sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06 \ - --hash=sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5 -pip==26.1 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:4e8486d821d814b77319acb7b9e8bf5a4ee7590a643e7cb21029f209be8573c1 \ - --hash=sha256:81e13ebcca3ffa8cc85e4deff5c27e1ee26dea0aa7fc2f294a073ac208806ff3 +pillow==11.1.0 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83 \ + --hash=sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96 \ + --hash=sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65 \ + --hash=sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a \ + --hash=sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352 \ + --hash=sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f \ + --hash=sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20 \ + --hash=sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c \ + --hash=sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114 \ + --hash=sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49 \ + --hash=sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91 \ + --hash=sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0 \ + --hash=sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2 \ + --hash=sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5 \ + --hash=sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884 \ + --hash=sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e \ + --hash=sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c \ + --hash=sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196 \ + --hash=sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756 \ + --hash=sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861 \ + --hash=sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269 \ + --hash=sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1 \ + --hash=sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb \ + --hash=sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a \ + --hash=sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081 \ + --hash=sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1 \ + --hash=sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8 \ + --hash=sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90 \ + --hash=sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc \ + --hash=sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5 \ + --hash=sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1 \ + --hash=sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3 \ + --hash=sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35 \ + --hash=sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f \ + --hash=sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c \ + --hash=sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2 \ + --hash=sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2 \ + --hash=sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf \ + --hash=sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65 \ + --hash=sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b \ + --hash=sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442 \ + --hash=sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2 \ + --hash=sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade \ + --hash=sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482 \ + --hash=sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe \ + --hash=sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc \ + --hash=sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a \ + --hash=sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec \ + --hash=sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3 \ + --hash=sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a \ + --hash=sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07 \ + --hash=sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6 \ + --hash=sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f \ + --hash=sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e \ + --hash=sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192 \ + --hash=sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0 \ + --hash=sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6 \ + --hash=sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73 \ + --hash=sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f \ + --hash=sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6 \ + --hash=sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547 \ + --hash=sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9 \ + --hash=sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457 \ + --hash=sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8 \ + --hash=sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26 \ + --hash=sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5 \ + --hash=sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab \ + --hash=sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070 \ + --hash=sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71 \ + --hash=sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9 \ + --hash=sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761 +pip==25.3 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:8d0538dbbd7babbd207f261ed969c65de439f6bc9e5dbd3b3b9a77f25d95f343 \ + --hash=sha256:9655943313a94722b7774661c21049070f6bbb0a1516bf02f7c8d5d9201514cd platformdirs==4.3.6 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb @@ -1883,9 +1849,9 @@ psycopg2-binary==2.9.10 ; python_version >= "3.10" and python_version < "4.0" \ pyasn1-modules==0.3.0 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:5bd01446b736eb9d31512a30d46c1ac3395d676c6f3cafa4c03eb54b9925631c \ --hash=sha256:d3ccd6ed470d9ffbc716be08bd90efbd44d0734bc9303818f7336070984a162d -pyasn1==0.6.3 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf \ - --hash=sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde +pyasn1==0.5.1 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:4439847c58d40b1d0a573d07e3856e95333f1976294494c325775aeca506eb58 \ + --hash=sha256:6d391a96e59b23130a5cfa74d6fd7f388dbbe26cc8f1edf39fdddf08d9d6676c pycparser==2.22 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc @@ -2080,9 +2046,9 @@ pyelftools==0.31 ; python_version >= "3.10" and python_version < "4.0" \ pygal==2.4.0 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:27abab93cbc31e21f3c6bdecc05bda6cd3570cbdbd8297b7caa6904051b50d72 \ --hash=sha256:9204f05380b02a8a32f9bf99d310b51aa2a932cba5b369f7a4dc3705f0a4ce83 -pygments==2.20.0 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \ - --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 +pygments==2.19.1 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f \ + --hash=sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c pyguacamole==0.11 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:7f8d8652ce2e86473d72a50e0c9d8a8e0c3c74e373c6b926ca4c851774cae608 \ --hash=sha256:d6facde097a1b1a3048b20fb2ff88b024744ceb2865fb912525da7ebb7779695 @@ -2146,15 +2112,15 @@ pynacl==1.5.0 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394 \ --hash=sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b \ --hash=sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543 -pyopenssl==26.0.0 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:df94d28498848b98cc1c0ffb8ef1e71e40210d3b0a8064c9d29571ed2904bf81 \ - --hash=sha256:f293934e52936f2e3413b89c6ce36df66a0b34ae1ea3a053b8c5020ff2f513fc +pyopenssl==25.0.0 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:424c247065e46e76a37411b9ab1782541c23bb658bf003772c3405fbaa128e90 \ + --hash=sha256:cd2cef799efa3936bb08e8ccb9433a575722b9dd986023f1cabc4ae64e9dac16 pyparsing==3.2.1 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:506ff4f4386c4cec0590ec19e6302d3aedb992fdc02c761e90416f158dacf8e1 \ --hash=sha256:61980854fd66de3a90028d679a954d5f2623e83144b5afe5ee86f43d762e5f0a -pypdf==6.10.2 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:7d09ce108eff6bf67465d461b6ef352dcb8d84f7a91befc02f904455c6eea11d \ - --hash=sha256:aa53be9826655b51c96741e5d7983ca224d898ac0a77896e64636810517624aa +pypdf==5.2.0 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:7c38e68420f038f2c4998fd9d6717b6db4f6cef1642e9cf384d519c9cf094663 \ + --hash=sha256:d107962ec45e65e3bd10c1d9242bdbbedaa38193c9e3a6617bd6d996e5747b19 pyre2==0.3.10 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:2771bc0a3a5f3fd1d34fe8ae80debd1fe59a1cdcecdbeb60818bff8deb247e56 \ --hash=sha256:29312fbb22b7d3cf3e522c43267098162e98ec8b4fc202c9260e22df671b42e1 \ @@ -2180,9 +2146,9 @@ pysocks==1.7.1 ; python_version >= "3.10" and python_version < "4.0" \ python-dateutil==2.9.0.post0 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 -python-dotenv==1.2.2 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a \ - --hash=sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3 +python-dotenv==1.0.1 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \ + --hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a python-flirt==0.9.2 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:113c0d865380117bbb5f52870b1459f9ee8fe8f6c5317d2206357c0834a2e9f3 \ --hash=sha256:191f325a137339db07b83112a3a1117d4a8db2c1d17c37bbc7b9078c3fc032c7 \ @@ -2304,9 +2270,9 @@ pyyaml==6.0.2 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \ --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 -pyzipper==0.4.0 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:a4b96afcac04c5589d5abdc6158dd362166374e3cc6810aa441e65f8a17cb9e3 \ - --hash=sha256:aa7b8a0fe741d67aac36ead85f6e735af107b72f84e0775f2ed565fc0d3a2f02 +pyzipper==0.3.6 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:0adca90a00c36a93fbe49bfa8c5add452bfe4ef85a1b8e3638739dd1c7b26bfc \ + --hash=sha256:6d097f465bfa47796b1494e12ea65d1478107d38e13bc56f6e58eedc4f6c1a87 questionary==2.1.1 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d \ --hash=sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59 @@ -2555,9 +2521,9 @@ sqlalchemy==2.0.41 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:dd5ec3aa6ae6e4d5b5de9357d2133c07be1aff6405b136dad753a16afb6717dd \ --hash=sha256:edba70118c4be3c2b1f90754d308d0b79c6fe2c0fdc52d8ddf603916f83f4db9 \ --hash=sha256:ff8e80c4c4932c10493ff97028decfdb622de69cae87e0f127a7ebe32b4069c6 -sqlparse==0.5.4 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:4396a7d3cf1cd679c1be976cf3dc6e0a51d0111e87787e7a8d780e7d5a998f9e \ - --hash=sha256:99a9f0314977b76d776a0fcb8554de91b9bb8a18560631d6bc48721d07023dcb +sqlparse==0.5.3 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272 \ + --hash=sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca storage3==0.12.1 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:32ea8f5eb2f7185c2114a4f6ae66d577722e32503f0a30b56e7ed5c7f13e6b48 \ --hash=sha256:9da77fd4f406b019fdcba201e9916aefbf615ef87f551253ce427d8136459a34 @@ -2633,9 +2599,9 @@ tomli==2.2.1 ; python_version == "3.10" \ --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 -twisted==26.4.0rc2 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:41eeb62a7f2688c634f7a1af76e225c58da48f8af1640fcb285ce386a96889e2 \ - --hash=sha256:d1db6c391a1b6e70028c3212298178621db149a6f75555f6503da557c763406d +twisted==24.11.0 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:695d0556d5ec579dcc464d2856b634880ed1319f45b10d19043f2b57eb0115b5 \ + --hash=sha256:fe403076c71f04d5d2d789a755b687c5637ec3bcd3b2b8252d76f2ba65f54261 txaio==23.1.1 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:aaea42f8aad50e0ecfb976130ada140797e9dcb85fad2cf72b0f37f8cefcb490 \ --hash=sha256:f9a9216e976e5e3246dfd112ad7ad55ca915606b60b84a757ac769bd404ff704 @@ -2660,9 +2626,9 @@ unicorn==2.1.1 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:b0f139adb1c9406f57d25cab96ad7a6d3cbb9119f5480ebecedd4f5d7cb024fb \ --hash=sha256:d4a08dbf222c5481bc909a9aa404b79874f6e67f5ba7c47036d03c68ab7371a7 \ --hash=sha256:f0ebcfaba67ef0ebcd05ee3560268f1c6f683bdd08ff496888741a163d29735d -urllib3==2.7.0 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \ - --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897 +urllib3==2.3.0 ; python_version >= "3.10" and python_version < "4.0" \ + --hash=sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df \ + --hash=sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d uvicorn==0.18.3 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af \ --hash=sha256:9a66e7c42a2a95222f76ec24a4b754c158261c4696e683b9dadc72b590e0311b