diff --git a/README.md b/README.md index 8b09ea5..6dd5d1b 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 a167d1c..7848929 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 58461e0..a2f04a3 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 fd8774e..2e2c9e1 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 6412b4d..5678bad 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 43a50d5..8ebde8d 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 9fd1049..2fdefd5 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 29cfa21..1eabc85 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 e958410..bdebf36 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 205aec0..42ca82d 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"