From 06f673e039b5a68d4c07c547f6553cb9c71e1631 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 4 May 2026 13:52:41 -0700 Subject: [PATCH 01/15] pass timeout through SDK diff requests Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- socketsecurity/socketcli.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/socketsecurity/socketcli.py b/socketsecurity/socketcli.py index 1f2b166..cd83d6f 100644 --- a/socketsecurity/socketcli.py +++ b/socketsecurity/socketcli.py @@ -26,6 +26,23 @@ load_dotenv() +DEFAULT_API_TIMEOUT = 1200 + + +def get_api_request_timeout(config: CliConfig) -> int: + return config.timeout if config.timeout is not None else DEFAULT_API_TIMEOUT + + +def build_socket_sdk(config: CliConfig) -> socketdev: + cli_user_agent_string = f"SocketPythonCLI/{config.version}" + return socketdev( + token=config.api_token, + timeout=get_api_request_timeout(config), + allow_unverified=config.allow_unverified, + user_agent=cli_user_agent_string + ) + + def cli(): try: main_code() @@ -63,8 +80,7 @@ def main_code(): "1. Command line: --api-token YOUR_TOKEN\n" "2. Environment variable: SOCKET_SECURITY_API_TOKEN") sys.exit(3) - cli_user_agent_string = f"SocketPythonCLI/{config.version}" - sdk = socketdev(token=config.api_token, allow_unverified=config.allow_unverified, user_agent=cli_user_agent_string) + sdk = build_socket_sdk(config) # Suppress urllib3 InsecureRequestWarning when using --allow-unverified if config.allow_unverified: @@ -83,7 +99,7 @@ def main_code(): socket_config = SocketConfig( api_key=config.api_token, allow_unverified_ssl=config.allow_unverified, - timeout=config.timeout if config.timeout is not None else 1200 # Use CLI timeout if provided + timeout=get_api_request_timeout(config) ) log.debug("loaded socket_config") client = CliClient(socket_config) From f7f21395b0f27bf91e842ff723f7c6612e61cc44 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 4 May 2026 13:53:08 -0700 Subject: [PATCH 02/15] respect license detail exclusion, disable-blocking on API failures Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- socketsecurity/core/__init__.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/socketsecurity/core/__init__.py b/socketsecurity/core/__init__.py index daaa9a8..b442b20 100644 --- a/socketsecurity/core/__init__.py +++ b/socketsecurity/core/__init__.py @@ -919,7 +919,8 @@ def get_license_text_via_purl(self, packages: dict[str, Package], batch_size: in def get_added_and_removed_packages( self, head_full_scan_id: str, - new_full_scan_id: str + new_full_scan_id: str, + include_license_details: bool = True ) -> Tuple[Dict[str, Package], Dict[str, Package], Dict[str, Package]]: """ Get packages that were added and removed between scans. @@ -936,17 +937,17 @@ def get_added_and_removed_packages( diff_start = time.time() try: diff_report = ( - self.sdk.fullscans.stream_diff - ( + self.sdk.fullscans.stream_diff( self.config.org_slug, head_full_scan_id, new_full_scan_id, - use_types=True + use_types=True, + include_license_details=str(include_license_details).lower() ).data ) except APIFailure as e: log.error(f"API Error: {e}") - sys.exit(1) + raise except Exception as e: import traceback log.error(f"Error getting diff report: {str(e)}") @@ -1149,7 +1150,11 @@ def create_new_diff( added_packages, removed_packages, packages - ) = self.get_added_and_removed_packages(head_full_scan_id, new_full_scan.id) + ) = self.get_added_and_removed_packages( + head_full_scan_id, + new_full_scan.id, + include_license_details=getattr(params, "include_license_details", True) + ) # Separate unchanged packages from added/removed for --strict-blocking support unchanged_packages = { From 9963016ccd5f7a391ef79b7f0be39f64f5db7707 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 4 May 2026 13:53:58 -0700 Subject: [PATCH 03/15] add tests to cover diff timeout, API failure behaviors Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- tests/core/test_sdk_methods.py | 21 ++++++++++++++++++++ tests/unit/test_cli_config.py | 26 +++++++++++++++++++++++- tests/unit/test_socketcli.py | 36 ++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_socketcli.py diff --git a/tests/core/test_sdk_methods.py b/tests/core/test_sdk_methods.py index bb096eb..0a3ff0d 100644 --- a/tests/core/test_sdk_methods.py +++ b/tests/core/test_sdk_methods.py @@ -1,4 +1,5 @@ import pytest +from socketdev.exceptions import APIFailure from socketdev.fullscans import FullScanParams from socketsecurity.core import Core @@ -101,6 +102,7 @@ def test_get_added_and_removed_packages(core): "head", "new", use_types=True, + include_license_details="true", ) # Verify the results @@ -115,6 +117,25 @@ def test_get_added_and_removed_packages(core): assert "dp2_t1" in removed # Verify transitive dependencies are also tracked assert "pypi/direct_package_1@1.6.0" in all_packages # Unchanged package is in full package map +def test_get_added_and_removed_packages_can_exclude_license_details(core): + """Test that diff scan license detail expansion can be disabled.""" + core.get_added_and_removed_packages("head", "new", include_license_details=False) + + core.sdk.fullscans.stream_diff.assert_called_once_with( + core.config.org_slug, + "head", + "new", + use_types=True, + include_license_details="false", + ) + +def test_get_added_and_removed_packages_reraises_api_failures(core): + """Test that API failures propagate to top-level CLI exit handling.""" + core.sdk.fullscans.stream_diff.side_effect = APIFailure("upstream request timeout") + + with pytest.raises(APIFailure): + core.get_added_and_removed_packages("head", "new") + def test_empty_alerts_preserved(core): """Test that empty alerts arrays stay as empty arrays and don't become None""" # Get the scan that contains dp2 (which has empty alerts array) diff --git a/tests/unit/test_cli_config.py b/tests/unit/test_cli_config.py index 045f0e4..c26d1c5 100644 --- a/tests/unit/test_cli_config.py +++ b/tests/unit/test_cli_config.py @@ -1,6 +1,9 @@ import pytest + +from socketsecurity import socketcli from socketsecurity.config import CliConfig + class TestCliConfig: def test_api_token_from_env(self, monkeypatch): monkeypatch.setenv("SOCKET_SECURITY_API_KEY", "test-token") @@ -81,4 +84,25 @@ def test_workspace_is_independent_of_workspace_name(self): "--workspace-name", "monorepo-suffix", ]) assert config.workspace == "my-workspace" - assert config.workspace_name == "monorepo-suffix" \ No newline at end of file + assert config.workspace_name == "monorepo-suffix" + + def test_api_request_timeout_defaults_to_twenty_minutes(self): + config = CliConfig.from_args(["--api-token", "test"]) + assert socketcli.get_api_request_timeout(config) == 1200 + + def test_socket_sdk_receives_cli_timeout(self, monkeypatch): + captured = {} + + def fake_socketdev(**kwargs): + captured.update(kwargs) + return object() + + monkeypatch.setattr(socketcli, "socketdev", fake_socketdev) + config = CliConfig.from_args(["--api-token", "test", "--timeout", "1800"]) + + socketcli.build_socket_sdk(config) + + assert captured["token"] == "test" + assert captured["timeout"] == 1800 + assert captured["allow_unverified"] is False + assert captured["user_agent"] == f"SocketPythonCLI/{config.version}" diff --git a/tests/unit/test_socketcli.py b/tests/unit/test_socketcli.py new file mode 100644 index 0000000..a469c96 --- /dev/null +++ b/tests/unit/test_socketcli.py @@ -0,0 +1,36 @@ +import sys + +import pytest +from socketdev.exceptions import APIFailure + +from socketsecurity import socketcli + + +def test_cli_honors_disable_blocking_for_api_failures(monkeypatch): + def fail_main_code(): + raise APIFailure("upstream request timeout") + + monkeypatch.setattr(socketcli, "main_code", fail_main_code) + monkeypatch.setattr( + sys, + "argv", + ["socketcli", "--api-token", "test", "--disable-blocking"], + ) + + with pytest.raises(SystemExit) as exc_info: + socketcli.cli() + + assert exc_info.value.code == 0 + + +def test_cli_returns_error_for_api_failures_without_disable_blocking(monkeypatch): + def fail_main_code(): + raise APIFailure("upstream request timeout") + + monkeypatch.setattr(socketcli, "main_code", fail_main_code) + monkeypatch.setattr(sys, "argv", ["socketcli", "--api-token", "test"]) + + with pytest.raises(SystemExit) as exc_info: + socketcli.cli() + + assert exc_info.value.code == 3 From 0245c6362d5d2930106214a41f576d928321e07d Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 4 May 2026 13:54:15 -0700 Subject: [PATCH 04/15] bump version to prepare for release Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- socketsecurity/__init__.py | 2 +- uv.lock | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2ea6c0..0dfab45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2.2.87 + +- Fixed diff scan API requests so `--timeout` is passed through to the Socket SDK request layer. +- Fixed `--exclude-license-details` so the full-scan diff comparison request sends `include_license_details=false`. +- Let diff comparison API failures propagate to top-level CLI exit handling so `--disable-blocking` is honored consistently. + ## 2.2.83 - Fixed branch detection in detached-HEAD CI checkouts. When `git name-rev --name-only HEAD` returned an output with a suffix operator (e.g. `remotes/origin/master~1`, `master^0`), the `~N`/`^N` was previously passed through as the branch name and rejected by the Socket API as an invalid Git ref. The suffix is now stripped before the prefix split, producing the bare branch name. diff --git a/pyproject.toml b/pyproject.toml index 49bb294..feaa859 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "socketsecurity" -version = "2.2.86" +version = "2.2.87" requires-python = ">= 3.11" license = {"file" = "LICENSE"} dependencies = [ diff --git a/socketsecurity/__init__.py b/socketsecurity/__init__.py index c816fab..69d6f7b 100644 --- a/socketsecurity/__init__.py +++ b/socketsecurity/__init__.py @@ -1,3 +1,3 @@ __author__ = 'socket.dev' -__version__ = '2.2.86' +__version__ = '2.2.87' USER_AGENT = f'SocketPythonCLI/{__version__}' diff --git a/uv.lock b/uv.lock index a90e6b2..5a67fa0 100644 --- a/uv.lock +++ b/uv.lock @@ -1168,7 +1168,7 @@ wheels = [ [[package]] name = "socketsecurity" -version = "2.2.86" +version = "2.2.87" source = { editable = "." } dependencies = [ { name = "bs4" }, From bafb0444ab9f660521198dd2519643c9548db0d4 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 18 May 2026 15:59:56 -0400 Subject: [PATCH 05/15] chore: gitignore .context/ for Conductor workspaces Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e01bafe..99fe8b6 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ test/ logs ai_testing/ verify_find_files_lazy_loading.py +.context/ From 6cbd247377ccb1458ee350ab57c8ff1be9dd3ad8 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 18 May 2026 16:00:56 -0400 Subject: [PATCH 06/15] chore(deps): bump 8 main app dependencies to dependabot-validated versions Bundles the following Dependabot PRs into uv.lock (regenerated): - urllib3 2.6.3 -> 2.7.0 (closes #200) - gitpython 3.1.46 -> 3.1.50 (closes #198) - python-dotenv 1.2.1 -> 1.2.2 (closes #190) - pytest 9.0.2 -> 9.0.3 (closes #188) - uv 0.9.21 -> 0.11.6 (closes #184) - cryptography 46.0.5 -> 46.0.7 (closes #181) - pygments 2.19.2 -> 2.20.0 (closes #177) - requests 2.32.5 -> 2.33.0 (closes #175) All eight target versions were verified through Socket Firewall (sfw) on the full transitive dependency tree (15 packages including transitive deps fetched clean; no malware/typosquat/supply-chain alerts). Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- uv.lock | 160 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 80 insertions(+), 80 deletions(-) diff --git a/uv.lock b/uv.lock index 5a67fa0..c7bca02 100644 --- a/uv.lock +++ b/uv.lock @@ -382,50 +382,50 @@ toml = [ [[package]] name = "cryptography" -version = "46.0.5" +version = "46.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, - { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, - { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, - { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, - { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, - { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, - { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" }, - { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" }, - { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" }, - { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" }, - { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" }, - { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" }, - { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" }, - { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" }, - { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, - { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, - { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, - { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, - { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, - { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, - { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, - { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, - { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, - { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/e9/6f/6cc6cc9955caa6eaf83660b0da2b077c7fe8ff9950a3c5e45d605038d439/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a", size = 4218321, upload-time = "2026-02-10T19:18:22.349Z" }, - { url = "https://files.pythonhosted.org/packages/3e/5d/c4da701939eeee699566a6c1367427ab91a8b7088cc2328c09dbee940415/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356", size = 4381786, upload-time = "2026-02-10T19:18:24.529Z" }, - { url = "https://files.pythonhosted.org/packages/ac/97/a538654732974a94ff96c1db621fa464f455c02d4bb7d2652f4edc21d600/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da", size = 4217990, upload-time = "2026-02-10T19:18:25.957Z" }, - { url = "https://files.pythonhosted.org/packages/ae/11/7e500d2dd3ba891197b9efd2da5454b74336d64a7cc419aa7327ab74e5f6/cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257", size = 4381252, upload-time = "2026-02-10T19:18:27.496Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, + { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, + { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, + { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, + { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" }, + { url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" }, + { url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" }, + { url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" }, + { url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" }, + { url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" }, + { url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, + { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ea/075aac6a84b7c271578d81a2f9968acb6e273002408729f2ddff517fed4a/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15", size = 4219700, upload-time = "2026-04-08T01:57:40.625Z" }, + { url = "https://files.pythonhosted.org/packages/6c/7b/1c55db7242b5e5612b29fc7a630e91ee7a6e3c8e7bf5406d22e206875fbd/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455", size = 4385982, upload-time = "2026-04-08T01:57:42.725Z" }, + { url = "https://files.pythonhosted.org/packages/cb/da/9870eec4b69c63ef5925bf7d8342b7e13bc2ee3d47791461c4e49ca212f4/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65", size = 4219115, upload-time = "2026-04-08T01:57:44.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/72/05aa5832b82dd341969e9a734d1812a6aadb088d9eb6f0430fc337cc5a8f/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968", size = 4385479, upload-time = "2026-04-08T01:57:46.86Z" }, ] [[package]] @@ -475,14 +475,14 @@ wheels = [ [[package]] name = "gitpython" -version = "3.1.46" +version = "3.1.50" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/f6/354ae6491228b5eb40e10d89c4d13c651fe1cf7556e35ebdded50cff57ce/gitpython-3.1.50.tar.gz", hash = "sha256:80da2d12504d52e1f998772dc5baf6e553f8d2fcfe1fcc226c9d9a2ee3372dcc", size = 219798, upload-time = "2026-05-06T04:01:26.571Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" }, + { url = "https://files.pythonhosted.org/packages/20/7a/1c6e3562dfd8950adbb11ffbc65d21e7c89d01a6e4f137fa981056de25c5/gitpython-3.1.50-py3-none-any.whl", hash = "sha256:d352abe2908d07355014abdd21ddf798c2a961469239afec4962e9da884858f9", size = 212507, upload-time = "2026-05-06T04:01:23.799Z" }, ] [[package]] @@ -877,11 +877,11 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] @@ -895,7 +895,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.0.2" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -904,9 +904,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] @@ -962,11 +962,11 @@ sdist = { url = "https://files.pythonhosted.org/packages/36/47/ab65fc1d682befc31 [[package]] name = "python-dotenv" -version = "1.2.1" +version = "1.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, ] [[package]] @@ -1049,7 +1049,7 @@ wheels = [ [[package]] name = "requests" -version = "2.32.5" +version = "2.33.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1057,9 +1057,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, ] [[package]] @@ -1346,11 +1346,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] [[package]] @@ -1367,28 +1367,28 @@ wheels = [ [[package]] name = "uv" -version = "0.9.21" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e2/2b/4e2090bc3a6265b445b3d31ca6fff20c6458d11145069f7e48ade3e2d75b/uv-0.9.21.tar.gz", hash = "sha256:aa4ca6ccd68e81b5ebaa3684d3c4df2b51a982ac16211eadf0707741d36e6488", size = 3834762, upload-time = "2025-12-30T16:12:51.927Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/26/0750c5bb1637ebefe1db0936dc76ead8ce97f17368cda950642bfd90fa3f/uv-0.9.21-py3-none-linux_armv6l.whl", hash = "sha256:0b330eaced2fd9d94e2a70f3bb6c8fd7beadc9d9bf9f1227eb14da44039c413a", size = 21266556, upload-time = "2025-12-30T16:12:47.311Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ef/f019466c1e367ea68003cf35f4d44cc328694ed4a59b6004aa7dcacb2b35/uv-0.9.21-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1d8e0940bddd37a55f4479d61adaa6b302b780d473f037fc084e48b09a1678e7", size = 20485648, upload-time = "2025-12-30T16:12:15.746Z" }, - { url = "https://files.pythonhosted.org/packages/2a/41/f735bd9a5b4848b6f4f1028e6d768f581559d68eddb6403eb0f19ca4c843/uv-0.9.21-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cb420ddab7bcdd12c2352d4b551ced428d104311c0b98ce205675ab5c97072db", size = 18986976, upload-time = "2025-12-30T16:12:25.034Z" }, - { url = "https://files.pythonhosted.org/packages/9a/5f/01d537e05927594dc379ff8bc04f8cde26384d25108a9f63758eae2a7936/uv-0.9.21-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:a36d164438a6310c9fceebd041d80f7cffcc63ba80a7c83ee98394fadf2b8545", size = 20819312, upload-time = "2025-12-30T16:12:41.802Z" }, - { url = "https://files.pythonhosted.org/packages/18/89/9497395f57e007a2daed8172042ecccade3ff5569fd367d093f49bd6a4a8/uv-0.9.21-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c0ad83ce874cbbf9eda569ba793a9fb70870db426e9862300db8cf2950a7fe3b", size = 20900227, upload-time = "2025-12-30T16:12:19.242Z" }, - { url = "https://files.pythonhosted.org/packages/04/61/a3f6dfc75d278cce96b370e00b6f03d73ec260e5304f622504848bad219d/uv-0.9.21-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9076191c934b813147060e4cd97e33a58999de0f9c46f8ac67f614e154dae5c8", size = 21965424, upload-time = "2025-12-30T16:12:01.589Z" }, - { url = "https://files.pythonhosted.org/packages/18/3e/344e8c1078cfea82159c6608b8694f24fdfe850ce329a4708c026cb8b0ff/uv-0.9.21-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2ce0f6aca91f7fbf1192e43c063f4de3666fd43126aacc71ff7d5a79f831af59", size = 23540343, upload-time = "2025-12-30T16:12:13.139Z" }, - { url = "https://files.pythonhosted.org/packages/7f/20/5826659a81526687c6e5b5507f3f79f4f4b7e3022f3efae2ba36b19864c3/uv-0.9.21-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b4817642d5ef248b74ca7be3505e5e012a06be050669b80d1f7ced5ad50d188", size = 23171564, upload-time = "2025-12-30T16:12:22.219Z" }, - { url = "https://files.pythonhosted.org/packages/a6/8d/404c54e019bb99ce474dc21e6b96c8a1351ba3c06e5e19fd8dcae0ba1899/uv-0.9.21-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fb42237fa309d79905fb73f653f63c1fe45a51193411c614b13512cf5506df3", size = 22202400, upload-time = "2025-12-30T16:12:04.612Z" }, - { url = "https://files.pythonhosted.org/packages/1a/f0/aa3d0081a2004050564364a1ef3277ddf889c9989a7278c0a9cce8284926/uv-0.9.21-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1d22f0ac03635d661e811c69d7c0b292751f90699acc6a1fb1509e17c936474", size = 22206448, upload-time = "2025-12-30T16:12:30.626Z" }, - { url = "https://files.pythonhosted.org/packages/fc/a9/7a375e723a588f31f305ddf9ae2097af0b9dc7f7813641788b5b9764a237/uv-0.9.21-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:cdd805909d360ad67640201376c8eb02de08dcf1680a1a81aebd9519daed6023", size = 20940568, upload-time = "2025-12-30T16:12:27.533Z" }, - { url = "https://files.pythonhosted.org/packages/18/d5/6187ffb7e1d24df34defe2718db8c4c3c08f153d3e7da22c250134b79cd1/uv-0.9.21-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:82e438595a609cbe4e45c413a54bd5756d37c8c39108ce7b2799aff15f7d3337", size = 22085077, upload-time = "2025-12-30T16:12:10.153Z" }, - { url = "https://files.pythonhosted.org/packages/ee/fa/8e211167d0690d9f15a08da610a0383d2f43a6c838890878e14948472284/uv-0.9.21-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:fc1c06e1e5df423e1517e350ea2c9d85ecefd0919188a0a9f19bd239bbbdeeaf", size = 20862893, upload-time = "2025-12-30T16:12:49.87Z" }, - { url = "https://files.pythonhosted.org/packages/33/b2/9d24d84cb9a1a6a5ea98d03a29abf800d87e5710d25e53896dc73aeb63a5/uv-0.9.21-py3-none-musllinux_1_1_i686.whl", hash = "sha256:9ef3d2a213c7720f4dae336e5123fe88427200d7523c78091c4ab7f849c3f13f", size = 21428397, upload-time = "2025-12-30T16:12:07.483Z" }, - { url = "https://files.pythonhosted.org/packages/4f/40/1e8e4c2e1308432c708eaa66dccdb83d2ee6120ea2b7d65e04fc06f48ff8/uv-0.9.21-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:8da20914d92ba4cc35f071414d3da7365294fc0b7114da8ac2ab3a86c695096f", size = 22450537, upload-time = "2025-12-30T16:12:33.36Z" }, - { url = "https://files.pythonhosted.org/packages/18/b8/99c4731d001f512e844dfdc740db2bf2fea56d538749b639d21f5117a74a/uv-0.9.21-py3-none-win32.whl", hash = "sha256:e716e23bc0ec8cbb0811f99e653745e0cf15223e7ba5d8857d46be5b40b3045b", size = 20032654, upload-time = "2025-12-30T16:12:36.007Z" }, - { url = "https://files.pythonhosted.org/packages/29/6b/da441bf335f5e1c0c100b7dfb9702b6fed367ba703e543037bf1e70bf8c3/uv-0.9.21-py3-none-win_amd64.whl", hash = "sha256:64a7bb0e4e6a4c2d98c2d55f42aead7c2df0ceb17d5911d1a42b76228cab4525", size = 22206744, upload-time = "2025-12-30T16:12:38.953Z" }, - { url = "https://files.pythonhosted.org/packages/98/02/afbed8309fe07aaa9fa58a98941cebffbcd300fe70499a02a6806d93517b/uv-0.9.21-py3-none-win_arm64.whl", hash = "sha256:6c13c40966812f6bd6ecb6546e5d3e27e7fe9cefa07018f074f51d703cb29e1c", size = 20591604, upload-time = "2025-12-30T16:12:44.634Z" }, +version = "0.11.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dd/f3/8aceeab67ea69805293ab290e7ca8cc1b61a064d28b8a35c76d8eba063dd/uv-0.11.6.tar.gz", hash = "sha256:e3b21b7e80024c95ff339fcd147ac6fc3dd98d3613c9d45d3a1f4fd1057f127b", size = 4073298, upload-time = "2026-04-09T12:09:01.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/fe/4b61a3d5ad9d02e8a4405026ccd43593d7044598e0fa47d892d4dafe44c9/uv-0.11.6-py3-none-linux_armv6l.whl", hash = "sha256:ada04dcf89ddea5b69d27ac9cdc5ef575a82f90a209a1392e930de504b2321d6", size = 23780079, upload-time = "2026-04-09T12:08:56.609Z" }, + { url = "https://files.pythonhosted.org/packages/52/db/d27519a9e1a5ffee9d71af1a811ad0e19ce7ab9ae815453bef39dd479389/uv-0.11.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5be013888420f96879c6e0d3081e7bcf51b539b034a01777041934457dfbedf3", size = 23214721, upload-time = "2026-04-09T12:09:32.228Z" }, + { url = "https://files.pythonhosted.org/packages/a6/8f/4399fa8b882bd7e0efffc829f73ab24d117d490a93e6bc7104a50282b854/uv-0.11.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:ffa5dc1cbb52bdce3b8447e83d1601a57ad4da6b523d77d4b47366db8b1ceb18", size = 21750109, upload-time = "2026-04-09T12:09:24.357Z" }, + { url = "https://files.pythonhosted.org/packages/32/07/5a12944c31c3dda253632da7a363edddb869ed47839d4d92a2dc5f546c93/uv-0.11.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:bfb107b4dade1d2c9e572992b06992d51dd5f2136eb8ceee9e62dd124289e825", size = 23551146, upload-time = "2026-04-09T12:09:10.439Z" }, + { url = "https://files.pythonhosted.org/packages/79/5b/2ec8b0af80acd1016ed596baf205ddc77b19ece288473b01926c4a9cf6db/uv-0.11.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:9e2fe7ce12161d8016b7deb1eaad7905a76ff7afec13383333ca75e0c4b5425d", size = 23331192, upload-time = "2026-04-09T12:09:34.792Z" }, + { url = "https://files.pythonhosted.org/packages/62/7d/eea35935f2112b21c296a3e42645f3e4b1aa8bcd34dcf13345fbd55134b7/uv-0.11.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7ed9c6f70c25e8dfeedddf4eddaf14d353f5e6b0eb43da9a14d3a1033d51d915", size = 23337686, upload-time = "2026-04-09T12:09:18.522Z" }, + { url = "https://files.pythonhosted.org/packages/21/47/2584f5ab618f6ebe9bdefb2f765f2ca8540e9d739667606a916b35449eec/uv-0.11.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d68a013e609cebf82077cbeeb0809ed5e205257814273bfd31e02fc0353bbfc2", size = 25008139, upload-time = "2026-04-09T12:09:03.983Z" }, + { url = "https://files.pythonhosted.org/packages/95/81/497ae5c1d36355b56b97dc59f550c7e89d0291c163a3f203c6f341dff195/uv-0.11.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93f736dddca03dae732c6fdea177328d3bc4bf137c75248f3d433c57416a4311", size = 25712458, upload-time = "2026-04-09T12:09:07.598Z" }, + { url = "https://files.pythonhosted.org/packages/3c/1c/74083238e4fab2672b63575b9008f1ea418b02a714bcfcf017f4f6a309b6/uv-0.11.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e96a66abe53fced0e3389008b8d2eff8278cfa8bb545d75631ae8ceb9c929aba", size = 24915507, upload-time = "2026-04-09T12:08:50.892Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ee/e14fe10ba455a823ed18233f12de6699a601890905420b5c504abf115116/uv-0.11.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b096311b2743b228df911a19532b3f18fa420bf9530547aecd6a8e04bbfaccd", size = 24971011, upload-time = "2026-04-09T12:08:54.016Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/7b9c83eaadf98e343317ff6384a7227a4855afd02cdaf9696bcc71ee6155/uv-0.11.6-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:904d537b4a6e798015b4a64ff5622023bd4601b43b6cd1e5f423d63471f5e948", size = 23640234, upload-time = "2026-04-09T12:09:15.735Z" }, + { url = "https://files.pythonhosted.org/packages/d6/51/75ccdd23e76ff1703b70eb82881cd5b4d2a954c9679f8ef7e0136ef2cfab/uv-0.11.6-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:4ed8150c26b5e319381d75ae2ce6aba1e9c65888f4850f4e3b3fa839953c90a5", size = 24452664, upload-time = "2026-04-09T12:09:26.875Z" }, + { url = "https://files.pythonhosted.org/packages/4d/86/ace80fe47d8d48b5e3b5aee0b6eb1a49deaacc2313782870250b3faa36f5/uv-0.11.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1c9218c8d4ac35ca6e617fb0951cc0ab2d907c91a6aea2617de0a5494cf162c0", size = 24494599, upload-time = "2026-04-09T12:09:37.368Z" }, + { url = "https://files.pythonhosted.org/packages/05/2d/4b642669b56648194f026de79bc992cbfc3ac2318b0a8d435f3c284934e8/uv-0.11.6-py3-none-musllinux_1_1_i686.whl", hash = "sha256:9e211c83cc890c569b86a4183fcf5f8b6f0c7adc33a839b699a98d30f1310d3a", size = 24159150, upload-time = "2026-04-09T12:09:13.17Z" }, + { url = "https://files.pythonhosted.org/packages/ae/24/7eecd76fe983a74fed1fc700a14882e70c4e857f1d562a9f2303d4286c12/uv-0.11.6-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:d2a1d2089afdf117ad19a4c1dd36b8189c00ae1ad4135d3bfbfced82342595cf", size = 25164324, upload-time = "2026-04-09T12:08:59.56Z" }, + { url = "https://files.pythonhosted.org/packages/27/e0/bbd4ba7c2e5067bbba617d87d306ec146889edaeeaa2081d3e122178ca08/uv-0.11.6-py3-none-win32.whl", hash = "sha256:6e8344f38fa29f85dcfd3e62dc35a700d2448f8e90381077ef393438dcd5012e", size = 22865693, upload-time = "2026-04-09T12:09:21.415Z" }, + { url = "https://files.pythonhosted.org/packages/a5/33/1983ce113c538a856f2d620d16e39691962ecceef091a84086c5785e32e5/uv-0.11.6-py3-none-win_amd64.whl", hash = "sha256:a28bea69c1186303d1200f155c7a28c449f8a4431e458fcf89360cc7ef546e40", size = 25371258, upload-time = "2026-04-09T12:09:40.52Z" }, + { url = "https://files.pythonhosted.org/packages/35/01/be0873f44b9c9bc250fcbf263367fcfc1f59feab996355bcb6b52fff080d/uv-0.11.6-py3-none-win_arm64.whl", hash = "sha256:a78f6d64b9950e24061bc7ec7f15ff8089ad7f5a976e7b65fcadce58fe02f613", size = 23869585, upload-time = "2026-04-09T12:09:29.425Z" }, ] [[package]] From 230b16eb97f0f82ae927704802e8c29202a06d53 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 18 May 2026 16:01:25 -0400 Subject: [PATCH 07/15] chore(deps): bump e2e fixture manifests Bundles three Dependabot PRs that target the e2e test fixtures used by the Socket scan/reachability validation suite: - tests/e2e/fixtures/simple-npm: axios 1.15.0 -> 1.15.2 (closes #196) - tests/e2e/fixtures/simple-pypi: requests 2.31.0 -> 2.33.0 (closes #187) - tests/e2e/fixtures/simple-pypi: flask 3.0.0 -> 3.1.3 (closes #186) These fixtures were stale (not intentionally pinned), so it's safe to bring them current. Socket Firewall verified install paths for both: - npm fixture: 228 packages fetched clean - pypi fixture: install path clean Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- tests/e2e/fixtures/simple-npm/package.json | 2 +- tests/e2e/fixtures/simple-pypi/requirements.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/fixtures/simple-npm/package.json b/tests/e2e/fixtures/simple-npm/package.json index 8a7e8ec..28152a4 100644 --- a/tests/e2e/fixtures/simple-npm/package.json +++ b/tests/e2e/fixtures/simple-npm/package.json @@ -6,7 +6,7 @@ "dependencies": { "lodash": "4.18.1", "express": "4.22.0", - "axios": "1.15.0" + "axios": "1.15.2" }, "devDependencies": { "typescript": "5.0.4", diff --git a/tests/e2e/fixtures/simple-pypi/requirements.txt b/tests/e2e/fixtures/simple-pypi/requirements.txt index acce997..28271a8 100644 --- a/tests/e2e/fixtures/simple-pypi/requirements.txt +++ b/tests/e2e/fixtures/simple-pypi/requirements.txt @@ -1,3 +1,3 @@ -requests==2.31.0 -flask==3.0.0 +requests==2.33.0 +flask==3.1.3 pyyaml==6.0.1 From 1f61a669b2425fbc23a5e5580da0271db96d79b7 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 18 May 2026 16:01:43 -0400 Subject: [PATCH 08/15] feat(config): auto-truncate commit messages over 200 chars The --commit-message flag passes its value directly into the API request URL as a query parameter with no length limit. AI-generated commit messages and the common CI pattern of concatenating $BUILDKITE_BUILD_NUMBER + $BUILDKITE_MESSAGE can easily exceed URL length limits, producing HTTP 413 errors. The 413 originates from an infrastructure-layer URL length limit (nginx/Cloudflare), not application-level validation -- confirmed via inspection of the Socket API route handler, which has no constraint on commit_message (unlike committers, which enforces <= 200 chars and returns a clean 400). 200 chars chosen as a conservative defensive ceiling given URL encoding can 2-3x raw character count. No customer should ever want a 2000-character commit message in their scan metadata. A backend-side validation (returning 400 instead of 413) is filed as a follow-on for the depscan API team. Motivated by customer incidents (Plaid). Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- socketsecurity/config.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/socketsecurity/config.py b/socketsecurity/config.py index 1d18c6a..b2000f1 100644 --- a/socketsecurity/config.py +++ b/socketsecurity/config.py @@ -177,6 +177,19 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig': if commit_message and commit_message.startswith('"') and commit_message.endswith('"'): commit_message = commit_message[1:-1] + # Truncate to avoid 413s from oversized URL query parameters. + # The API has no application-layer length validation on commit_message; + # the 413 originates from an infrastructure-layer URL length limit + # (nginx/Cloudflare). 200 chars chosen as a conservative ceiling given + # URL encoding can 2-3x raw character count. + MAX_COMMIT_MESSAGE_LENGTH = 200 + if commit_message and len(commit_message) > MAX_COMMIT_MESSAGE_LENGTH: + logging.debug( + f"commit_message truncated from {len(commit_message)} to " + f"{MAX_COMMIT_MESSAGE_LENGTH} characters to avoid API request size limits" + ) + commit_message = commit_message[:MAX_COMMIT_MESSAGE_LENGTH] + config_args = { 'api_token': api_token, 'repo': args.repo, From 90411b5ee09e7efbdada29afcb02ffb853463906 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 18 May 2026 16:04:06 -0400 Subject: [PATCH 09/15] feat: differentiated exit code 3 for infrastructure errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds explicit handling and structured logging for API/infrastructure failures so they are distinguishable from real blocking security findings in CI. Exit code semantics (NEW for 2.3.0): 0 - Clean scan, no blocking issues (or --disable-blocking) 1 - Blocking security finding(s) detected 2 - Process interrupted (SIGINT) -- already in place 3 - Infrastructure / API error -- NEW 5 - Warning-level alerts only -- preserved This is a BREAKING change for any pipeline that previously caught exit 1 to mean "anything went wrong." Such pipelines now need to handle 3 separately for infrastructure failures, or use --exit-code-on-api-error to remap. Changes: - socketsecurity/core/__init__.py * Import RequestTimeoutExceeded and `requests` * Wrap fullscans.stream_diff with requests.exceptions.Timeout -> RequestTimeoutExceeded * Wrap fullscans.post (create_full_scan) with the same pattern - socketsecurity/socketcli.py * Import RequestTimeoutExceeded + APIFailure * IS_BUILDKITE constant (gates BK-specific markers per spec §3) * New _emit_infrastructure_error helper emits Buildkite log section markers (^^^ +++ and ---) when BUILDKITE=true, plus a soft_fail hint; bare log.error otherwise. Markers go to stdout via print() so they aren't prefixed with log formatting; markers are literal strings on other CI platforms so the gate is required. * Explicit RequestTimeoutExceeded and APIFailure handlers added before the generic Exception handler, all using config.exit_code_on_api_error - socketsecurity/config.py * New CliConfig field: exit_code_on_api_error (default 3) * New flag: --exit-code-on-api-error Customers can remap to 0 (swallow), 100 (Buildkite soft_fail), etc. Note: --disable-blocking now only zeroes out exit 1 (security findings), not exit 3 (infrastructure). This separation is the whole point of the new code -- callers who want to also swallow infra errors should use --exit-code-on-api-error 0. Motivated by customer incidents (Plaid 413s and timeouts; Anthropic 'other' SocketCategory crash). Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- socketsecurity/config.py | 16 ++++++++ socketsecurity/core/__init__.py | 16 +++++++- socketsecurity/socketcli.py | 68 +++++++++++++++++++++++++++++---- 3 files changed, 90 insertions(+), 10 deletions(-) diff --git a/socketsecurity/config.py b/socketsecurity/config.py index b2000f1..16a8ef0 100644 --- a/socketsecurity/config.py +++ b/socketsecurity/config.py @@ -98,6 +98,7 @@ class CliConfig: pending_head: bool = False enable_diff: bool = False timeout: Optional[int] = 1200 + exit_code_on_api_error: int = 3 exclude_license_details: bool = False include_module_folders: bool = False repo_is_public: bool = False @@ -224,6 +225,7 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig': 'integration_type': args.integration, 'pending_head': args.pending_head, 'timeout': args.timeout, + 'exit_code_on_api_error': args.exit_code_on_api_error, 'exclude_license_details': args.exclude_license_details, 'include_module_folders': args.include_module_folders, 'repo_is_public': args.repo_is_public, @@ -754,6 +756,20 @@ def create_argument_parser() -> argparse.ArgumentParser: help="Timeout in seconds for API requests", required=False ) + advanced_group.add_argument( + "--exit-code-on-api-error", + dest="exit_code_on_api_error", + type=int, + default=3, + metavar="", + help=( + "Exit code to use when the CLI encounters an API or infrastructure " + "error (timeout, network failure, unexpected exception). Default: 3. " + "Use this to distinguish infrastructure failures from security findings " + "in your CI pipeline. Example for Buildkite soft_fail: set to 100. " + "Set to 0 to swallow infrastructure errors entirely." + ) + ) advanced_group.add_argument( "--allow-unverified", action="store_true", diff --git a/socketsecurity/core/__init__.py b/socketsecurity/core/__init__.py index b442b20..acf7756 100644 --- a/socketsecurity/core/__init__.py +++ b/socketsecurity/core/__init__.py @@ -11,6 +11,7 @@ if TYPE_CHECKING: from socketsecurity.config import CliConfig +import requests from socketdev import socketdev from socketdev.exceptions import APIFailure from socketdev.fullscans import FullScanParams, SocketArtifact @@ -26,7 +27,7 @@ Package, Purl ) -from socketsecurity.core.exceptions import APIResourceNotFound +from socketsecurity.core.exceptions import APIResourceNotFound, RequestTimeoutExceeded from .socket_config import SocketConfig from .utils import socket_globs from .resource_utils import check_file_count_against_ulimit @@ -538,7 +539,13 @@ def create_full_scan(self, files: List[str], params: FullScanParams, base_paths: log.info("Creating new full scan") create_full_start = time.time() - res = self.sdk.fullscans.post(files, params, use_types=True, use_lazy_loading=True, max_open_files=50, base_paths=base_paths) + try: + res = self.sdk.fullscans.post(files, params, use_types=True, use_lazy_loading=True, max_open_files=50, base_paths=base_paths) + except requests.exceptions.Timeout as e: + raise RequestTimeoutExceeded( + f"Request timed out while creating full scan for org " + f"'{self.config.org_slug}': {e}" + ) if not res.success: log.error(f"Error creating full scan: {res.message}, status: {res.status}") raise Exception(f"Error creating full scan: {res.message}, status: {res.status}") @@ -945,6 +952,11 @@ def get_added_and_removed_packages( include_license_details=str(include_license_details).lower() ).data ) + except requests.exceptions.Timeout as e: + raise RequestTimeoutExceeded( + f"Request timed out while comparing scans " + f"(head: {head_full_scan_id}, new: {new_full_scan_id}): {e}" + ) except APIFailure as e: log.error(f"API Error: {e}") raise diff --git a/socketsecurity/socketcli.py b/socketsecurity/socketcli.py index cd83d6f..a84251c 100644 --- a/socketsecurity/socketcli.py +++ b/socketsecurity/socketcli.py @@ -10,11 +10,13 @@ from dotenv import load_dotenv from git import InvalidGitRepositoryError, NoSuchPathError from socketdev import socketdev +from socketdev.exceptions import APIFailure from socketdev.fullscans import FullScanParams from socketsecurity.config import CliConfig from socketsecurity.core import Core from socketsecurity.core.classes import Diff from socketsecurity.core.cli_client import CliClient +from socketsecurity.core.exceptions import RequestTimeoutExceeded from socketsecurity.core.git_interface import Git from socketsecurity.core.logging import initialize_logging, set_debug_mode from socketsecurity.core.messages import Messages @@ -28,6 +30,11 @@ DEFAULT_API_TIMEOUT = 1200 +# Buildkite sets BUILDKITE=true in every job environment. Used to gate +# log section markers (^^^ +++, ---) that render as literal strings in +# GitHub Actions / GitLab CI / other platforms. +IS_BUILDKITE = os.getenv("BUILDKITE") == "true" + def get_api_request_timeout(config: CliConfig) -> int: return config.timeout if config.timeout is not None else DEFAULT_API_TIMEOUT @@ -43,6 +50,39 @@ def build_socket_sdk(config: CliConfig) -> socketdev: ) +def _emit_infrastructure_error( + message: str, + hint: str = None, + include_traceback: bool = False, +) -> None: + """Emit a structured error for infrastructure failures. + + Uses Buildkite log section markers when running in Buildkite so the + error auto-expands in the BK UI. Markers go to stdout via print() + (not log.error) so they're not prefixed with log formatting. + """ + if IS_BUILDKITE: + # ^^^ +++ retroactively opens the current log section so the error + # is visible immediately without manual expansion. + print("^^^ +++", flush=True) + print("--- :warning: Socket Infrastructure Error", flush=True) + + log.error(message) + + if hint: + log.error(hint) + + if IS_BUILDKITE: + log.error( + "Tip: to prevent this from blocking your pipeline, add " + "`soft_fail: [{exit_status: 3}]` to this step, or use " + "`--exit-code-on-api-error` to set a custom exit code." + ) + + if include_traceback: + traceback.print_exc() + + def cli(): try: main_code() @@ -53,15 +93,27 @@ def cli(): sys.exit(2) else: sys.exit(0) + except RequestTimeoutExceeded as error: + config = CliConfig.from_args() + _emit_infrastructure_error( + f"Request timed out: {error}", + hint="This is an infrastructure issue, not a security finding.", + ) + sys.exit(config.exit_code_on_api_error) + except APIFailure as error: + config = CliConfig.from_args() + _emit_infrastructure_error( + f"API error: {error}", + hint="This is an infrastructure issue, not a security finding.", + ) + sys.exit(config.exit_code_on_api_error) except Exception as error: - log.error("Unexpected error when running the cli") - log.error(error) - traceback.print_exc() - config = CliConfig.from_args() # Get current config - if not config.disable_blocking: - sys.exit(3) - else: - sys.exit(0) + config = CliConfig.from_args() + _emit_infrastructure_error( + f"Unexpected error when running the CLI: {error}", + include_traceback=True, + ) + sys.exit(config.exit_code_on_api_error) def main_code(): From 38a642c7b60802699a56b843d469e46541818009 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 18 May 2026 16:28:16 -0400 Subject: [PATCH 10/15] test: cover commit truncation, exit code remap, and Buildkite formatting Adds and updates unit tests for the v2.3.0 behavior changes: tests/unit/test_cli_config.py - Commit message under 200 chars passes through unchanged - Commit message over 200 chars truncated to exactly 200 - Quote-strip runs BEFORE truncation (250-char inner -> 200) - --exit-code-on-api-error defaults to 3 - --exit-code-on-api-error accepts custom and zero values tests/unit/test_socketcli.py - APIFailure -> exit 3 by default - APIFailure -> exit 3 EVEN with --disable-blocking (breaking change for 2.3.0: --disable-blocking only affects security findings now) - RequestTimeoutExceeded -> exit 3 by default - --exit-code-on-api-error 100 remaps timeout to exit 100 - --exit-code-on-api-error 0 swallows infrastructure errors - Generic RuntimeError uses exit_code_on_api_error too - _emit_infrastructure_error: * BUILDKITE=true emits "^^^ +++" and "--- :warning:" markers (stdout) plus a soft_fail hint (logger) * Without BUILDKITE, no markers and no soft_fail hint * Traceback only included when include_traceback=True Replaces the old test_cli_honors_disable_blocking_for_api_failures test which encoded the pre-2.3.0 coupling between --disable-blocking and infra errors -- that coupling is gone by design. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- tests/unit/test_cli_config.py | 36 +++++++++ tests/unit/test_socketcli.py | 144 ++++++++++++++++++++++++++++++++-- 2 files changed, 174 insertions(+), 6 deletions(-) diff --git a/tests/unit/test_cli_config.py b/tests/unit/test_cli_config.py index c26d1c5..727bbf2 100644 --- a/tests/unit/test_cli_config.py +++ b/tests/unit/test_cli_config.py @@ -90,6 +90,42 @@ def test_api_request_timeout_defaults_to_twenty_minutes(self): config = CliConfig.from_args(["--api-token", "test"]) assert socketcli.get_api_request_timeout(config) == 1200 + def test_commit_message_passes_through_under_limit(self): + msg = "short commit message" + config = CliConfig.from_args(["--api-token", "test", "--commit-message", msg]) + assert config.commit_message == msg + + def test_commit_message_truncated_above_limit(self): + # 250 chars -> must truncate to 200 + msg = "a" * 250 + config = CliConfig.from_args(["--api-token", "test", "--commit-message", msg]) + assert config.commit_message is not None + assert len(config.commit_message) == 200 + assert config.commit_message == "a" * 200 + + def test_commit_message_quote_strip_runs_before_truncation(self): + # Quoted message of 250 inner chars -> quote-stripped, then truncated to 200. + inner = "b" * 250 + quoted = f'"{inner}"' + config = CliConfig.from_args(["--api-token", "test", "--commit-message", quoted]) + assert config.commit_message == "b" * 200 + + def test_exit_code_on_api_error_default_is_3(self): + config = CliConfig.from_args(["--api-token", "test"]) + assert config.exit_code_on_api_error == 3 + + def test_exit_code_on_api_error_accepts_custom_value(self): + config = CliConfig.from_args( + ["--api-token", "test", "--exit-code-on-api-error", "100"] + ) + assert config.exit_code_on_api_error == 100 + + def test_exit_code_on_api_error_accepts_zero(self): + config = CliConfig.from_args( + ["--api-token", "test", "--exit-code-on-api-error", "0"] + ) + assert config.exit_code_on_api_error == 0 + def test_socket_sdk_receives_cli_timeout(self, monkeypatch): captured = {} diff --git a/tests/unit/test_socketcli.py b/tests/unit/test_socketcli.py index a469c96..f0bfa28 100644 --- a/tests/unit/test_socketcli.py +++ b/tests/unit/test_socketcli.py @@ -4,17 +4,84 @@ from socketdev.exceptions import APIFailure from socketsecurity import socketcli +from socketsecurity.core.exceptions import RequestTimeoutExceeded -def test_cli_honors_disable_blocking_for_api_failures(monkeypatch): +# --------------------------------------------------------------------------- +# Exit code semantics (spec v2.3.0): infrastructure errors map to +# config.exit_code_on_api_error (default 3), INDEPENDENT of --disable-blocking. +# --------------------------------------------------------------------------- + + +def test_api_failure_exits_with_default_exit_code_on_api_error(monkeypatch): + def fail_main_code(): + raise APIFailure("upstream request timeout") + + monkeypatch.setattr(socketcli, "main_code", fail_main_code) + monkeypatch.setattr(sys, "argv", ["socketcli", "--api-token", "test"]) + + with pytest.raises(SystemExit) as exc_info: + socketcli.cli() + + assert exc_info.value.code == 3 + + +def test_api_failure_exits_3_even_with_disable_blocking(monkeypatch): + # Breaking change for 2.3.0: --disable-blocking no longer zeroes out + # infrastructure errors. Use --exit-code-on-api-error 0 for that. def fail_main_code(): raise APIFailure("upstream request timeout") + monkeypatch.setattr(socketcli, "main_code", fail_main_code) + monkeypatch.setattr( + sys, "argv", ["socketcli", "--api-token", "test", "--disable-blocking"] + ) + + with pytest.raises(SystemExit) as exc_info: + socketcli.cli() + + assert exc_info.value.code == 3 + + +def test_request_timeout_exceeded_exits_with_configured_code(monkeypatch): + def fail_main_code(): + raise RequestTimeoutExceeded("scan diff timed out after 1200s") + + monkeypatch.setattr(socketcli, "main_code", fail_main_code) + monkeypatch.setattr(sys, "argv", ["socketcli", "--api-token", "test"]) + + with pytest.raises(SystemExit) as exc_info: + socketcli.cli() + + assert exc_info.value.code == 3 + + +def test_exit_code_on_api_error_remaps_timeout(monkeypatch): + def fail_main_code(): + raise RequestTimeoutExceeded("scan diff timed out after 1200s") + monkeypatch.setattr(socketcli, "main_code", fail_main_code) monkeypatch.setattr( sys, "argv", - ["socketcli", "--api-token", "test", "--disable-blocking"], + ["socketcli", "--api-token", "test", "--exit-code-on-api-error", "100"], + ) + + with pytest.raises(SystemExit) as exc_info: + socketcli.cli() + + assert exc_info.value.code == 100 + + +def test_exit_code_on_api_error_zero_swallows_infrastructure_errors(monkeypatch): + def fail_main_code(): + raise RequestTimeoutExceeded("scan diff timed out after 1200s") + + monkeypatch.setattr(socketcli, "main_code", fail_main_code) + monkeypatch.setattr( + sys, + "argv", + ["socketcli", "--api-token", "test", "--exit-code-on-api-error", "0"], ) with pytest.raises(SystemExit) as exc_info: @@ -23,14 +90,79 @@ def fail_main_code(): assert exc_info.value.code == 0 -def test_cli_returns_error_for_api_failures_without_disable_blocking(monkeypatch): +def test_generic_exception_uses_exit_code_on_api_error(monkeypatch): def fail_main_code(): - raise APIFailure("upstream request timeout") + raise RuntimeError("unexpected boom") monkeypatch.setattr(socketcli, "main_code", fail_main_code) - monkeypatch.setattr(sys, "argv", ["socketcli", "--api-token", "test"]) + monkeypatch.setattr( + sys, + "argv", + ["socketcli", "--api-token", "test", "--exit-code-on-api-error", "7"], + ) with pytest.raises(SystemExit) as exc_info: socketcli.cli() - assert exc_info.value.code == 3 + assert exc_info.value.code == 7 + + +# --------------------------------------------------------------------------- +# Buildkite-aware log formatting (spec §3): ^^^ +++ / --- markers only +# emitted when BUILDKITE=true; bare log.error otherwise. +# --------------------------------------------------------------------------- + + +def test_emit_infrastructure_error_no_buildkite_has_no_markers( + monkeypatch, capsys, caplog +): + monkeypatch.setattr(socketcli, "IS_BUILDKITE", False) + with caplog.at_level("ERROR", logger="socketcli"): + socketcli._emit_infrastructure_error( + "something failed", hint="just so you know" + ) + captured = capsys.readouterr() + assert "^^^ +++" not in captured.out + assert "--- :warning:" not in captured.out + log_text = "\n".join(r.getMessage() for r in caplog.records) + assert "soft_fail" not in log_text + + +def test_emit_infrastructure_error_buildkite_emits_markers( + monkeypatch, capsys, caplog +): + monkeypatch.setattr(socketcli, "IS_BUILDKITE", True) + with caplog.at_level("ERROR", logger="socketcli"): + socketcli._emit_infrastructure_error( + "something failed", hint="just so you know" + ) + captured = capsys.readouterr() + # Markers go to stdout via print() so pytest's capsys catches them cleanly. + assert "^^^ +++" in captured.out + assert "--- :warning: Socket Infrastructure Error" in captured.out + # The soft_fail tip is appended via log.error() -- caplog captures it. + log_text = "\n".join(r.getMessage() for r in caplog.records) + assert "soft_fail" in log_text + + +def test_emit_infrastructure_error_omits_traceback_by_default(monkeypatch, capsys): + monkeypatch.setattr(socketcli, "IS_BUILDKITE", False) + try: + raise ValueError("boom") + except ValueError: + socketcli._emit_infrastructure_error("wrapped", include_traceback=False) + captured = capsys.readouterr() + assert "Traceback" not in captured.err + assert "Traceback" not in captured.out + + +def test_emit_infrastructure_error_includes_traceback_on_request(monkeypatch, capsys): + monkeypatch.setattr(socketcli, "IS_BUILDKITE", False) + try: + raise ValueError("boom") + except ValueError: + socketcli._emit_infrastructure_error("wrapped", include_traceback=True) + captured = capsys.readouterr() + # traceback.print_exc() writes to sys.stderr by default. + assert "Traceback" in captured.err + assert "ValueError: boom" in captured.err From 2f69c1ccb28e7ae614d9b446390790af4627ca9b Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 18 May 2026 16:29:05 -0400 Subject: [PATCH 11/15] ci: add .github/dependabot.yml to tame Dependabot PR noise The repo had no explicit Dependabot config, so Dependabot ran on full defaults: one PR per package per manifest, across every manifest in the tree -- including the e2e test fixtures that are intentionally crafted to exercise Socket's scanner. The cumulative result was the "PR pileup" this PR is consolidating. New config: - uv ecosystem (main app): grouped weekly into ONE minor/patch PR and one major PR; matches the existing python:uv labeling - github-actions: grouped weekly into ONE minor/patch PR - docker: separate weekly PR per Dockerfile change - 7-day cooldown across all ecosystems to give upstream time to pull bad releases - e2e fixtures (tests/e2e/fixtures/{simple-npm,simple-pypi}) are INTENTIONALLY excluded -- their pins should be chosen for supply- chain signal, not auto-bumped (this is why we had three fixture PRs in the cleanup) Pattern adapted from SocketDev/socket-basics. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- .github/dependabot.yml | 80 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a231603 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,80 @@ +# Dependabot configuration for socket-python-cli. +# +# Design notes: +# - Python deps are grouped into ONE weekly PR (minor/patch) and a separate +# PR for major bumps. Drastically reduces PR clutter compared to the +# default behavior of one PR per package. +# - GitHub Actions are grouped similarly into one weekly PR. +# - Docker (the project Dockerfile) is tracked separately. +# - The e2e test fixtures under `tests/e2e/fixtures/` are INTENTIONALLY +# omitted: those manifests exist to exercise Socket scanning and should +# be chosen for the supply-chain signal they expose, not auto-bumped. +# - 7-day cooldown across all ecosystems gives upstream maintainers time +# to pull bad releases before we receive a PR. + +version: 2 +updates: + + # Main app Python deps (uv-tracked) + - package-ecosystem: "uv" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 2 + groups: + python-minor-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + python-major: + patterns: + - "*" + update-types: + - "major" + labels: + - "dependencies" + - "python:uv" + commit-message: + prefix: "chore" + include: "scope" + cooldown: + default-days: 7 + + # GitHub Actions used in workflows + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 2 + groups: + github-actions-minor-patch: + patterns: + - "*" + update-types: + - "minor" + - "patch" + labels: + - "dependencies" + - "github-actions" + commit-message: + prefix: "ci" + include: "scope" + cooldown: + default-days: 7 + + # Project Dockerfile base images and pinned binaries + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 2 + labels: + - "dependencies" + - "docker" + commit-message: + prefix: "chore" + include: "scope" + cooldown: + default-days: 7 From e400f1437bb83436237b37fc314318d07ee50589 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 18 May 2026 16:29:57 -0400 Subject: [PATCH 12/15] ci: add dependabot-review workflow with Socket Firewall smoke jobs For every Dependabot-authored PR, inspect what changed and conditionally run Socket Firewall (sfw) install smoke jobs against the affected manifests. Because sfw uses the anonymous Socket public-data API it needs NO secret, so this runs cleanly under the standard `pull_request` context -- no pull_request_target, no token-leak surface. Jobs (all conditional on file diff): - python-sfw-smoke: pyproject.toml / uv.lock -> `sfw uv sync` plus an import smoke on the modules that depend on the upgraded packages (cryptography, gitpython, requests, ...). Catches API-removal breaks from minor/patch deprecations. - fixture-npm-sfw-smoke: tests/e2e/fixtures/simple-npm/** -> `sfw npm install` in a clean cwd. - fixture-pypi-sfw-smoke: tests/e2e/fixtures/simple-pypi/** -> `sfw pip install -r requirements.txt` in a clean venv. - dockerfile-smoke: `docker build --pull` (no push) when the Dockerfile changes. - workflow-notice: Flag Dependabot PRs that touch workflow or dependabot config files for explicit human review (anti-supply-chain-confusion guardrail). Pattern adapted from SocketDev/socket-basics dependabot-review.yml. Action SHAs match the pins already in python-tests.yml and e2e-test.yml so zizmor stays happy. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- .github/workflows/dependabot-review.yml | 205 ++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 .github/workflows/dependabot-review.yml diff --git a/.github/workflows/dependabot-review.yml b/.github/workflows/dependabot-review.yml new file mode 100644 index 0000000..486ccb3 --- /dev/null +++ b/.github/workflows/dependabot-review.yml @@ -0,0 +1,205 @@ +name: dependabot-review + +# Dependency-update PR guardrails for Dependabot-authored PRs. +# +# Runs only on PRs opened by dependabot[bot]. Inspects which files +# changed, then conditionally runs Socket Firewall (sfw) install smoke +# jobs for the affected manifests. Because sfw uses the free, anonymous +# Socket public-data path it needs NO API key, so we can run it from +# the unprivileged `pull_request` context without pull_request_target +# or any of its security tradeoffs. +# +# Pattern adapted from SocketDev/socket-basics. + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +permissions: + contents: read + +concurrency: + group: dependabot-review-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + inspect: + if: github.event.pull_request.user.login == 'dependabot[bot]' + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + python_deps_changed: ${{ steps.diff.outputs.python_deps_changed }} + fixture_npm_changed: ${{ steps.diff.outputs.fixture_npm_changed }} + fixture_pypi_changed: ${{ steps.diff.outputs.fixture_pypi_changed }} + dockerfile_changed: ${{ steps.diff.outputs.dockerfile_changed }} + workflow_or_action_changed: ${{ steps.diff.outputs.workflow_or_action_changed }} + steps: + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Inspect changed files + id: diff + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + CHANGED_FILES="$(git diff --name-only "$BASE_SHA" "$HEAD_SHA")" + + { + echo "## Changed files" + echo '```' + printf '%s\n' "$CHANGED_FILES" + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + has_file() { + local pattern="$1" + if printf '%s\n' "$CHANGED_FILES" | grep -Eq "$pattern"; then + echo "true" + else + echo "false" + fi + } + + { + echo "python_deps_changed=$(has_file '^(pyproject\.toml|uv\.lock)$')" + echo "fixture_npm_changed=$(has_file '^tests/e2e/fixtures/simple-npm/')" + echo "fixture_pypi_changed=$(has_file '^tests/e2e/fixtures/simple-pypi/')" + echo "dockerfile_changed=$(has_file '^Dockerfile$')" + echo "workflow_or_action_changed=$(has_file '^\.github/workflows/|^\.github/dependabot\.yml$')" + } >> "$GITHUB_OUTPUT" + + - name: Summarize review expectations + env: + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + { + echo "## Dependabot Review Checklist" + echo "- PR: $PR_URL" + echo "- Confirm upstream release notes before merge" + echo "- Do not treat a Dependabot PR as trusted solely because of the actor" + echo "- This workflow runs in pull_request context only; no publish secrets are exposed" + } >> "$GITHUB_STEP_SUMMARY" + + python-sfw-smoke: + needs: inspect + if: needs.inspect.outputs.python_deps_changed == 'true' + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + with: + fetch-depth: 1 + persist-credentials: false + + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + with: + python-version: "3.12" + + - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af + with: + node-version: "20" + + - name: Install Socket Firewall + run: npm install -g sfw + + - name: Install uv + run: python -m pip install --upgrade pip uv + + - name: Sync project through Socket Firewall + run: sfw uv sync --extra test --extra dev + + - name: Import smoke test + run: | + uv run python -c " + from socketsecurity.socketcli import cli, build_socket_sdk + from socketsecurity.core import Core + from socketsecurity.core.exceptions import ( + APIFailure, RequestTimeoutExceeded, APIResourceNotFound, + ) + from socketsecurity.core.git_interface import Git + from socketsecurity.config import CliConfig + print('import smoke OK') + " + + fixture-npm-sfw-smoke: + needs: inspect + if: needs.inspect.outputs.fixture_npm_changed == 'true' + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + with: + fetch-depth: 1 + persist-credentials: false + + - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af + with: + node-version: "20" + + - name: Install Socket Firewall + run: npm install -g sfw + + - name: Install fixture through Socket Firewall + working-directory: tests/e2e/fixtures/simple-npm + run: sfw npm install --no-audit --no-fund --ignore-scripts + + fixture-pypi-sfw-smoke: + needs: inspect + if: needs.inspect.outputs.fixture_pypi_changed == 'true' + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + with: + fetch-depth: 1 + persist-credentials: false + + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 + with: + python-version: "3.12" + + - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af + with: + node-version: "20" + + - name: Install Socket Firewall + run: npm install -g sfw + + - name: Install fixture through Socket Firewall + working-directory: tests/e2e/fixtures/simple-pypi + run: | + python -m venv .venv + # shellcheck disable=SC1091 + source .venv/bin/activate + sfw pip install -r requirements.txt + + dockerfile-smoke: + needs: inspect + if: needs.inspect.outputs.dockerfile_changed == 'true' + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + with: + fetch-depth: 1 + persist-credentials: false + + - name: Build the Dockerfile (no push) + run: docker build --pull -t socket-python-cli:dependabot-smoke . + + workflow-notice: + needs: inspect + if: needs.inspect.outputs.workflow_or_action_changed == 'true' + runs-on: ubuntu-latest + timeout-minutes: 2 + steps: + - name: Flag workflow-sensitive updates + run: | + { + echo "## Sensitive File Notice" + echo "This Dependabot PR changes workflow or dependabot config files." + echo "Require explicit human review before merge." + } >> "$GITHUB_STEP_SUMMARY" From cd4a90048ea032e20e4ab200746af06476f98844 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 18 May 2026 16:30:20 -0400 Subject: [PATCH 13/15] ci: add lock-drift, import-smoke, and pip-audit; skip e2e on dependabot python-tests.yml: - `uv lock --locked` -- fails if uv.lock has drifted from pyproject.toml. Prevents the "forgot to commit the lockfile" class of mistake. - Import smoke step that loads every top-level module touching the upgraded packages (cryptography, gitpython, requests, urllib3, ...). Catches API-removal breaks from minor/patch deprecations that the unit suite alone wouldn't surface. - `uvx pip-audit --strict` against the synced env -- light CVE check on the resolved transitive tree. Runs in seconds via uv's caching. e2e-test.yml: - Skip e2e on Dependabot PRs. They don't have access to the Socket API secret so e2e would always fail on them, polluting the PR check UI. Supply-chain risk for dep bumps is covered by dependabot-review.yml's Socket Firewall smoke jobs, which need no secrets. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- .github/workflows/e2e-test.yml | 9 ++++++++- .github/workflows/python-tests.yml | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index 95e93c9..83a6fa4 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -11,7 +11,14 @@ permissions: jobs: e2e: - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository + # Skip e2e on: + # - PRs from forks (no secrets) + # - Dependabot PRs (no secrets, and dependency-bump risk is already + # covered by dependabot-review.yml's Socket Firewall smoke jobs) + if: >- + (github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository) && + github.event.pull_request.user.login != 'dependabot[bot]' runs-on: ubuntu-latest strategy: fail-fast: false diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index d30e67a..d2f0563 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -48,8 +48,24 @@ jobs: python -m pip install --upgrade pip pip install uv uv sync --extra test + - name: 🔒 verify uv.lock is in sync with pyproject.toml + run: uv lock --locked - name: 🧪 run tests run: uv run pytest -q tests/unit/ tests/core/ + - name: 💨 import smoke (catches API-removal breaks from upgraded deps) + run: | + uv run python -c " + from socketsecurity.socketcli import cli, build_socket_sdk, _emit_infrastructure_error + from socketsecurity.core import Core + from socketsecurity.core.exceptions import ( + APIFailure, RequestTimeoutExceeded, APIResourceNotFound, + ) + from socketsecurity.core.git_interface import Git + from socketsecurity.config import CliConfig + print('import smoke OK') + " + - name: 🛡️ pip-audit (known CVEs in the synced env) + run: uvx pip-audit --strict --disable-pip --progress-spinner off unsupported-python-install: runs-on: ubuntu-latest From f077cf72e47d09537534ad7e3002655c39b1e2d5 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 18 May 2026 16:31:42 -0400 Subject: [PATCH 14/15] chore(release): 2.3.0 -- exit code semantics, BK formatting, dep bumps Bumps the project version from 2.2.86 to 2.3.0 (minor) to signal the breaking exit-code change in this PR: Exit 1 == blocking security finding (previously: anything) Exit 3 == infrastructure / API error (NEW) CHANGELOG.md: - Breaking-change callout for the exit code semantics shift - --exit-code-on-api-error documentation - Commit message auto-truncation note - Buildkite log formatting note - Bundled Dependabot bumps roll-up - CI hardening summary (dependabot.yml, dependabot-review.yml, python-tests guards, e2e skip-on-dependabot) README.md: - New "Exit codes" section with the canonical table - Buildkite soft_fail examples (default exit 3 and custom 100) pyproject.toml + socketsecurity/__init__.py + uv.lock: - 2.2.87 (from the in-flight branch) -> 2.3.0 Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- CHANGELOG.md | 58 ++++++++++++++++++++++++++++++++++++++ README.md | 36 +++++++++++++++++++++++ pyproject.toml | 2 +- socketsecurity/__init__.py | 2 +- uv.lock | 2 +- 5 files changed, 97 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dfab45..26af2e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,63 @@ # Changelog +## 2.3.0 + +### Breaking change: exit codes for infrastructure errors + +API and infrastructure errors (timeouts, network failures, unexpected exceptions) +now exit with code `3` instead of `1`. Exit code `1` is now exclusively used for +blocking security findings. + +`--disable-blocking` no longer zeroes out infrastructure errors -- it only affects +exit code `1` (security findings). If your pipeline relied on `--disable-blocking` +to also swallow infra errors, use `--exit-code-on-api-error 0` instead. + +If you have pipeline logic that checks `exit_code == 1` to catch any CLI failure, +update it to handle `3` separately for infrastructure errors. See the exit code +reference in the README. + +### New: `--exit-code-on-api-error` + +New flag to remap the infrastructure error exit code. Useful for Buildkite +`soft_fail` configs or pipelines with existing exit-code conventions: + +``` +socketcli --exit-code-on-api-error 100 ... +``` + +Set to `0` to swallow infrastructure errors entirely. + +### New: commit message auto-truncation + +`--commit-message` values longer than 200 characters are now automatically +truncated before being sent to the API. This prevents HTTP 413 errors from +oversized URL query parameters -- common when using AI-generated commit +messages or piping in `$BUILDKITE_MESSAGE`. + +### Improved: Buildkite log formatting + +When running inside a Buildkite job (`BUILDKITE=true`), infrastructure errors +now emit Buildkite log section markers (`^^^ +++` and `--- :warning:`) so the +error section auto-expands in the Buildkite UI, along with a tip on using +`soft_fail` to prevent blocking. + +### Dependencies + +Bundles eight Dependabot main-app upgrades (closes #175, #177, #181, #184, #188, +#190, #198, #200) and three e2e fixture upgrades (closes #186, #187, #196). +All target versions verified through Socket Firewall (`sfw`). + +### CI / Internal + +- New `.github/dependabot.yml` with grouped weekly bumps and a 7-day cooldown; + e2e fixtures are intentionally excluded. +- New `dependabot-review` workflow runs Socket Firewall install smoke jobs on + every Dependabot PR -- no API secret required. +- `python-tests` workflow now runs `uv lock --locked` drift check, a top-level + import smoke step, and `pip-audit`. +- `e2e-test` workflow skips on Dependabot PRs (which can't access secrets); + Socket Firewall covers the supply-chain check. + ## 2.2.87 - Fixed diff scan API requests so `--timeout` is passed through to the Socket SDK request layer. diff --git a/README.md b/README.md index 7929189..30b3125 100644 --- a/README.md +++ b/README.md @@ -164,6 +164,42 @@ Minimal pattern: SOCKET_SECURITY_API_TOKEN: ${{ secrets.SOCKET_SECURITY_API_TOKEN }} ``` +## Exit codes + +| Code | Meaning | +|------|---------| +| `0` | Clean scan -- no blocking issues found (or `--disable-blocking` set) | +| `1` | Blocking security finding(s) detected | +| `2` | Scan interrupted (SIGINT / Ctrl+C) | +| `3` | Infrastructure or API error (timeout, network failure, unexpected error) | + +Exit code `3` is a Socket convention, not an industry standard. Use +`--exit-code-on-api-error ` to remap it -- e.g. to a Buildkite +`soft_fail` code, or to `0` to swallow infrastructure errors entirely. + +### Buildkite `soft_fail` example + +To prevent infrastructure errors from blocking PRs while still failing on +real security findings: + +```yaml +steps: + - label: ":lock: Socket Security Scan" + command: "socketcli ..." + soft_fail: + - exit_status: 3 +``` + +Or with a custom exit code: + +```yaml +steps: + - label: ":lock: Socket Security Scan" + command: "socketcli --exit-code-on-api-error 100 ..." + soft_fail: + - exit_status: 100 +``` + ## Common gotchas See [`docs/troubleshooting.md`](https://github.com/SocketDev/socket-python-cli/blob/main/docs/troubleshooting.md#common-gotchas). diff --git a/pyproject.toml b/pyproject.toml index feaa859..cf409eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "socketsecurity" -version = "2.2.87" +version = "2.3.0" requires-python = ">= 3.11" license = {"file" = "LICENSE"} dependencies = [ diff --git a/socketsecurity/__init__.py b/socketsecurity/__init__.py index 69d6f7b..10f2993 100644 --- a/socketsecurity/__init__.py +++ b/socketsecurity/__init__.py @@ -1,3 +1,3 @@ __author__ = 'socket.dev' -__version__ = '2.2.87' +__version__ = '2.3.0' USER_AGENT = f'SocketPythonCLI/{__version__}' diff --git a/uv.lock b/uv.lock index c7bca02..426e43b 100644 --- a/uv.lock +++ b/uv.lock @@ -1168,7 +1168,7 @@ wheels = [ [[package]] name = "socketsecurity" -version = "2.2.87" +version = "2.3.0" source = { editable = "." } dependencies = [ { name = "bs4" }, From 0ecfdf0261472f4fb7980442828c8796e3d30ce9 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 18 May 2026 16:32:44 -0400 Subject: [PATCH 15/15] ci: fix pip-audit invocation to scan exported requirements `uvx pip-audit --disable-pip` requires `-r` plus either hashed requirements or `--no-deps`. The previous invocation crashed at start. Now: export the locked deps via `uv export --no-hashes --no-emit-project` into a tmp requirements file (skipping the local editable install of the project itself), then feed that to pip-audit with `--disable-pip --no-deps`. Verified locally -- no known vulnerabilities found across the 85 locked transitive deps. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- .github/workflows/python-tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index d2f0563..4ce2434 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -64,8 +64,10 @@ jobs: from socketsecurity.config import CliConfig print('import smoke OK') " - - name: 🛡️ pip-audit (known CVEs in the synced env) - run: uvx pip-audit --strict --disable-pip --progress-spinner off + - name: 🛡️ pip-audit (known CVEs in the locked deps) + run: | + uv export --no-hashes --no-emit-project --format requirements-txt > /tmp/req-audit.txt + uvx pip-audit --strict --progress-spinner off --disable-pip --no-deps -r /tmp/req-audit.txt unsupported-python-install: runs-on: ubuntu-latest