diff --git a/README.md b/README.md index 8b09ea5d..6dd5d1b9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Socket Security CLI -The Socket Security CLI was created to enable integrations with other tools like GitHub Actions, GitLab, BitBucket, local use cases and more. The tool will get the head scan for the provided repo from Socket, create a new one, and then report any new alerts detected. If there are new alerts against the Socket security policy it'll exit with a non-Zero exit code. +The Socket Security CLI was created to enable integrations with other tools like GitHub Actions, GitLab, BitBucket, local use cases and more. The tool will get the head scan for the provided repo from Socket, create a new one, and then report any new alerts detected. If there are new alerts with blocking actions it'll exit with a non-Zero exit code. ## Quick Start diff --git a/pyproject.toml b/pyproject.toml index a167d1c6..78489291 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "socketsecurity" -version = "2.2.40" +version = "2.2.43" requires-python = ">= 3.10" license = {"file" = "LICENSE"} dependencies = [ @@ -160,3 +160,8 @@ docstring-code-line-length = "dynamic" [tool.hatch.build.targets.wheel] include = ["socketsecurity", "LICENSE"] + +[dependency-groups] +dev = [ + "pre-commit>=4.3.0", +] diff --git a/socketsecurity/__init__.py b/socketsecurity/__init__.py index 58461e04..a2f04a30 100644 --- a/socketsecurity/__init__.py +++ b/socketsecurity/__init__.py @@ -1,3 +1,3 @@ __author__ = 'socket.dev' -__version__ = '2.2.40' +__version__ = '2.2.43' USER_AGENT = f'SocketPythonCLI/{__version__}' diff --git a/socketsecurity/core/__init__.py b/socketsecurity/core/__init__.py index fd8774e9..2e2c9e13 100644 --- a/socketsecurity/core/__init__.py +++ b/socketsecurity/core/__init__.py @@ -19,7 +19,6 @@ from socketdev.fullscans import FullScanParams, SocketArtifact from socketdev.org import Organization from socketdev.repos import RepositoryInfo -from socketdev.settings import SecurityPolicyRule import copy from socketsecurity import __version__, USER_AGENT from socketsecurity.core.classes import ( @@ -82,8 +81,6 @@ def set_org_vars(self) -> None: self.config.full_scan_path = f"{base_path}/full-scans" self.config.repository_path = f"{base_path}/repos" - self.config.security_policy = self.get_security_policy() - def get_org_id_slug(self) -> Tuple[str, str]: """Gets the Org ID and Org Slug for the API Token.""" response = self.sdk.org.get(use_types=True) @@ -112,16 +109,7 @@ def get_sbom_data_list(self, artifacts_dict: Dict[str, SocketArtifact]) -> list[ """Converts artifacts dictionary to a list.""" return list(artifacts_dict.values()) - def get_security_policy(self) -> Dict[str, SecurityPolicyRule]: - """Gets the organization's security policy.""" - response = self.sdk.settings.get(self.config.org_slug, use_types=True) - - if not response.success: - log.error(f"Failed to get security policy: {response.status}") - log.error(response.message) - raise Exception(f"Failed to get security policy: {response.status}, message: {response.message}") - return response.securityPolicyRules def create_sbom_output(self, diff: Diff) -> dict: """Creates CycloneDX output for a given diff.""" @@ -1317,8 +1305,9 @@ def add_package_alerts_to_collection(self, package: Package, alerts_collection: url=package.url ) - if alert.type in self.config.security_policy: - action = self.config.security_policy[alert.type]['action'] + # Use action from API (from security policy, label policy, triage, etc.) + if 'action' in alert_item and alert_item['action']: + action = alert_item['action'] setattr(issue_alert, action, True) if issue_alert.key not in alerts_collection: diff --git a/socketsecurity/core/messages.py b/socketsecurity/core/messages.py index 6412b4dc..5678bad8 100644 --- a/socketsecurity/core/messages.py +++ b/socketsecurity/core/messages.py @@ -416,7 +416,7 @@ def security_comment_template(diff: Diff, config=None) -> str: > **❗️ Caution** > **Review the following alerts detected in dependencies.** > -> According to your organization's Security Policy, you **must** resolve all **"Block"** alerts before proceeding. It's recommended to resolve **"Warn"** alerts too. +> According to your organization's policies, you **must** resolve all **"Block"** alerts before proceeding. It's recommended to resolve **"Warn"** alerts too. > Learn more about [Socket for GitHub](https://socket.dev?utm_medium=gh). @@ -622,7 +622,7 @@ def create_acceptable_risk(md: MdUtils, ignore_commands: list) -> MdUtils: @staticmethod def create_security_alert_table(diff: Diff, md: MdUtils) -> tuple[MdUtils, list, dict]: """ - Creates the detected issues table based on the Security Policy + Creates the detected issues table based on alert actions from the API :param diff: Diff - Diff report with the detected issues :param md: MdUtils - Main markdown variable :return: @@ -794,7 +794,7 @@ def create_purl_link(details: Purl) -> str: @staticmethod def create_console_security_alert_table(diff: Diff) -> PrettyTable: """ - Creates the detected issues table based on the Security Policy + Creates the detected issues table based on alert actions from the API :param diff: Diff - Diff report with the detected issues :return: """ diff --git a/socketsecurity/core/socket_config.py b/socketsecurity/core/socket_config.py index 43a50d5a..8ebde8d6 100644 --- a/socketsecurity/core/socket_config.py +++ b/socketsecurity/core/socket_config.py @@ -25,7 +25,6 @@ class SocketConfig: org_slug: Optional[str] = None full_scan_path: Optional[str] = None repository_path: Optional[str] = None - security_policy: Dict = None repo_visibility: Optional[str] = 'private' all_issues: Optional['AllIssues'] = None excluded_dirs: Set[str] = field(default_factory=lambda: default_exclude_dirs) @@ -42,10 +41,6 @@ def __post_init__(self): self._validate_api_url(self.api_url) - # Initialize empty dict for security policy if None - if self.security_policy is None: - self.security_policy = {} - # Initialize AllIssues if None if self.all_issues is None: self.all_issues = AllIssues() @@ -70,6 +65,3 @@ def update_org_details(self, org_id: str, org_slug: str) -> None: self.full_scan_path = f"{base_path}/full-scans" self.repository_path = f"{base_path}/repos" - def update_security_policy(self, policy: Dict) -> None: - """Update security policy""" - self.security_policy = policy \ No newline at end of file diff --git a/tests/core/conftest.py b/tests/core/conftest.py index 9fd1049c..2fdefd58 100644 --- a/tests/core/conftest.py +++ b/tests/core/conftest.py @@ -10,7 +10,6 @@ StreamDiffResponse, ) from socketdev.repos import GetRepoResponse -from socketdev.settings import OrgSecurityPolicyResponse @pytest.fixture @@ -88,14 +87,7 @@ def stream_diff_response(data_dir, load_json): }) -@pytest.fixture -def security_policy(data_dir, load_json): - json_data = load_json(data_dir / "settings" / "security-policy.json") - return OrgSecurityPolicyResponse.from_dict({ - "success": json_data["success"], - "status": json_data["status"], - "securityPolicyRules": json_data["securityPolicyRules"] - }) + @pytest.fixture @@ -146,13 +138,11 @@ def mock_sdk_with_responses( new_scan_metadata, new_scan_stream, stream_diff_response, - security_policy, create_full_scan_response, ): sdk = mock_socket_sdk.return_value # Simple returns - sdk.settings.get.return_value = security_policy sdk.fullscans.post.return_value = create_full_scan_response # Argument-based returns diff --git a/tests/core/test_package_and_alerts.py b/tests/core/test_package_and_alerts.py index 29cfa21a..1eabc853 100644 --- a/tests/core/test_package_and_alerts.py +++ b/tests/core/test_package_and_alerts.py @@ -33,11 +33,10 @@ def mock_sdk(self): } }) - # Set up settings.get() to return empty security policy + # Set up settings.get() to return empty response mock.settings = Mock() settings_response = Mock() settings_response.success = True - settings_response.security_policy = {} mock.settings.get = Mock(return_value=settings_response) return mock @@ -48,7 +47,6 @@ def config(self): api_key="test-key", allow_unverified_ssl=False ) - config.security_policy = {} # Initialize with empty dict return config @pytest.fixture @@ -135,34 +133,7 @@ def test_add_package_alerts_basic(self, core): assert alert.type == "networkAccess" assert alert.severity == "high" - def test_add_package_alerts_with_security_policy(self, core): - """Test alerts are properly tagged based on security policy""" - # Mock security policy in config - core.config.security_policy = { - "networkAccess": {"action": "error"} - } - - package = Package( - id="pkg:npm/test@1.0.0", - name="test", - version="1.0.0", - type="npm", - alerts=[{ - "type": "networkAccess", - "key": "test-alert", - "severity": "high" - }], - topLevelAncestors=[] - ) - - alerts_collection = {} - packages = {package.id: package} - - result = core.add_package_alerts_to_collection(package, alerts_collection, packages) - - assert len(result) == 1 - alert = result["test-alert"][0] - assert alert.error is True + def test_get_capabilities_for_added_packages(self, core): """Test capability extraction from package alerts""" diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index e9584109..bdebf360 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -7,13 +7,12 @@ def test_config_default_values(): assert config.api_key == "test_key" assert config.api_url == "https://api.socket.dev/v0" - assert config.timeout == 30 + assert config.timeout == 1200 assert config.allow_unverified_ssl is False assert config.org_id is None assert config.org_slug is None assert config.full_scan_path is None assert config.repository_path is None - assert config.security_policy == {} def test_config_custom_values(): """Test that config accepts custom values""" @@ -67,14 +66,4 @@ def test_config_update_org_details(): assert config.full_scan_path == "orgs/test-org/full-scans" assert config.repository_path == "orgs/test-org/repos" -def test_config_update_security_policy(): - """Test updating security policy""" - config = SocketConfig(api_key="test_key") - - test_policy = { - "rule1": {"action": "block"}, - "rule2": {"action": "warn"} - } - config.security_policy = test_policy - assert config.security_policy == test_policy diff --git a/uv.lock b/uv.lock index 205aec0d..42ca82d3 100644 --- a/uv.lock +++ b/uv.lock @@ -1052,28 +1052,26 @@ wheels = [ [[package]] name = "socketdev" -version = "3.0.17" +version = "3.0.21" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/47/60/54b56ac179a9c89b2c9f2ab7eb5ba81220de64d11d52cf19249113ff364d/socketdev-3.0.17.tar.gz", hash = "sha256:a4446a84856c637c312d809d5b8deb25dd20ca38ae7d00a4c8104ea5b890c0af", size = 134013, upload-time = "2025-11-07T22:38:34.354Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/fb/4669dcd763144f7ebba824562b58648be08f93474ce12fbe3e21836e622f/socketdev-3.0.21.tar.gz", hash = "sha256:c5fe8bdba8c2c114e3bfff9f5f3a4224eca5c85f86a68f68dda8a2d3fea26815", size = 134528, upload-time = "2025-11-27T17:27:09.608Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/56/34ab0e33b5345ca7ada68cd0a9e9d4adcde16051192eb10f8e2c3e0deaa1/socketdev-3.0.17-py3-none-any.whl", hash = "sha256:0986ee0694d5ce879cadb8e06fcfb75a4ca2dfb6f415414593825701593cf991", size = 59317, upload-time = "2025-11-07T22:38:32.704Z" }, + { url = "https://files.pythonhosted.org/packages/e3/40/2974cca90077b861206e8f402571047ac074f6233c524eb88e8ee9323ecc/socketdev-3.0.21-py3-none-any.whl", hash = "sha256:39a85991445a4a37b0a3bc05138d5799cefc3185b77177fdb1e0d9a2ed81fd08", size = 59698, upload-time = "2025-11-27T17:27:07.696Z" }, ] [[package]] name = "socketsecurity" -version = "2.2.26" +version = "2.2.41" source = { editable = "." } dependencies = [ { name = "bs4" }, { name = "gitpython" }, - { name = "hatch" }, { name = "mdutils" }, { name = "packaging" }, - { name = "pluggy" }, { name = "prettytable" }, { name = "python-dotenv" }, { name = "requests" }, @@ -1096,15 +1094,18 @@ test = [ { name = "pytest-watch" }, ] +[package.dev-dependencies] +dev = [ + { name = "pre-commit" }, +] + [package.metadata] requires-dist = [ { name = "bs4", specifier = ">=0.0.2" }, { name = "gitpython" }, - { name = "hatch", specifier = ">=1.14.1" }, { name = "hatch", marker = "extra == 'dev'" }, { name = "mdutils" }, { name = "packaging" }, - { name = "pluggy", specifier = ">=1.6.0" }, { name = "pre-commit", marker = "extra == 'dev'" }, { name = "prettytable" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=7.4.0" }, @@ -1115,12 +1116,15 @@ requires-dist = [ { name = "python-dotenv" }, { name = "requests" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.3.0" }, - { name = "socketdev", specifier = ">=3.0.17,<4.0.0" }, + { name = "socketdev", specifier = ">=3.0.21,<4.0.0" }, { name = "twine", marker = "extra == 'dev'" }, { name = "uv", marker = "extra == 'dev'", specifier = ">=0.1.0" }, ] provides-extras = ["test", "dev"] +[package.metadata.requires-dev] +dev = [{ name = "pre-commit", specifier = ">=4.3.0" }] + [[package]] name = "soupsieve" version = "2.8"