Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
2 changes: 1 addition & 1 deletion socketsecurity/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__author__ = 'socket.dev'
__version__ = '2.2.86'
__version__ = '2.2.87'
USER_AGENT = f'SocketPythonCLI/{__version__}'
17 changes: 11 additions & 6 deletions socketsecurity/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)}")
Expand Down Expand Up @@ -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 = {
Expand Down
22 changes: 19 additions & 3 deletions socketsecurity/socketcli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down
21 changes: 21 additions & 0 deletions tests/core/test_sdk_methods.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
from socketdev.exceptions import APIFailure
from socketdev.fullscans import FullScanParams

from socketsecurity.core import Core
Expand Down Expand Up @@ -101,6 +102,7 @@ def test_get_added_and_removed_packages(core):
"head",
"new",
use_types=True,
include_license_details="true",
)

# Verify the results
Expand All @@ -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)
Expand Down
26 changes: 25 additions & 1 deletion tests/unit/test_cli_config.py
Original file line number Diff line number Diff line change
@@ -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")
Expand Down Expand Up @@ -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"
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}"
36 changes: 36 additions & 0 deletions tests/unit/test_socketcli.py
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading