diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index 0c0a755..12b38da 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -118,7 +118,7 @@ jobs: VERSION: ${{ env.VERSION }} run: | for i in {1..30}; do - if pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple socketdev==${VERSION}; then + if pip install --index-url 'https://test.pypi.org/simple/' --extra-index-url 'https://pypi.org/simple' socketdev==${VERSION}; then echo "Package ${VERSION} is now available and installable on Test PyPI" pip uninstall -y socketdev echo "success=true" >> $GITHUB_OUTPUT diff --git a/.gitignore b/.gitignore index 36e5b27..9f566db 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ dist *.dist *.egg-info *.cpython-312.pyc -example-socket-export.py \ No newline at end of file +example-socket-export.py +__pycache__/ \ No newline at end of file diff --git a/README.rst b/README.rst index 32d050d..7ba2530 100644 --- a/README.rst +++ b/README.rst @@ -304,6 +304,46 @@ Get GitHub Flavored Markdown diff between two full scans. - **before (str)** - The base full scan ID - **after (str)** - The comparison full scan ID +basics.get_config(org_slug, use_types) +"""""""""""""""""""""""""""""""""""""" +Get Socket Basics configuration for an organization. Socket Basics is a CI/CD security scanning suite that includes SAST scanning, secret detection, container security, and dependency analysis. + +**Usage:** + +.. code-block:: python + + from socketdev import socketdev + socket = socketdev(token="REPLACE_ME") + + # Basic usage - returns dictionary + config = socket.basics.get_config("org_slug") + print(f"Python SAST enabled: {config['pythonSastEnabled']}") + print(f"Secret scanning enabled: {config['secretScanningEnabled']}") + + # Using typed response objects + from socketdev.basics import SocketBasicsConfig, SocketBasicsResponse + response = socket.basics.get_config("org_slug", use_types=True) + if response.success and response.config: + print(f"JavaScript SAST: {response.config.javascriptSastEnabled}") + print(f"Trivy scanning: {response.config.trivyImageEnabled}") + +**PARAMETERS:** + +- **org_slug (str)** - The organization name +- **use_types (bool)** - Whether to return typed response objects (default: False) + +**Socket Basics Features:** + +- **Python SAST** - Static analysis for Python code +- **Go SAST** - Static analysis for Go code +- **JavaScript SAST** - Static analysis for JavaScript/TypeScript code +- **Secret Scanning** - Detection of hardcoded secrets and credentials +- **Trivy Image Scanning** - Vulnerability scanning for Docker images +- **Trivy Dockerfile Scanning** - Vulnerability scanning for Dockerfiles +- **Socket SCA** - Supply chain analysis for dependencies +- **Socket Scanning** - General dependency security scanning +- **Additional Parameters** - Custom configuration options + dependencies.get(limit, offset) """"""""""""""""""""""""""""""" Retrieve the dependencies for the organization associated with the API Key diff --git a/pyproject.toml b/pyproject.toml index 5bbdb6a..0339235 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.0.7" +version = "3.0.13" requires-python = ">= 3.9" dependencies = [ 'requests', diff --git a/socketdev/__init__.py b/socketdev/__init__.py index 74a0f61..820f7fa 100644 --- a/socketdev/__init__.py +++ b/socketdev/__init__.py @@ -24,6 +24,7 @@ from socketdev.auditlog import AuditLog from socketdev.analytics import Analytics from socketdev.alerttypes import AlertTypes +from socketdev.basics import Basics from socketdev.log import log __author__ = "socket.dev" @@ -72,6 +73,7 @@ def __init__(self, token: str, timeout: int = 1200): self.auditlog = AuditLog(self.api) self.analytics = Analytics(self.api) self.alerttypes = AlertTypes(self.api) + self.basics = Basics(self.api) @staticmethod def set_timeout(timeout: int): diff --git a/socketdev/basics/__init__.py b/socketdev/basics/__init__.py new file mode 100644 index 0000000..a512e73 --- /dev/null +++ b/socketdev/basics/__init__.py @@ -0,0 +1,120 @@ +import logging +from typing import Optional, Union +from dataclasses import dataclass, asdict + +log = logging.getLogger("socketdev") + + +@dataclass +class SocketBasicsConfig: + """Data class representing Socket Basics configuration settings.""" + pythonSastEnabled: bool = False + golangSastEnabled: bool = False + javascriptSastEnabled: bool = False + secretScanningEnabled: bool = False + trivyImageEnabled: bool = False + trivyDockerfileEnabled: bool = False + socketScanningEnabled: bool = False + socketScaEnabled: bool = False + additionalParameters: str = "" + + def __getitem__(self, key): + return getattr(self, key) + + def to_dict(self): + return asdict(self) + + @classmethod + def from_dict(cls, data: dict) -> "SocketBasicsConfig": + return cls( + pythonSastEnabled=data.get("pythonSastEnabled", False), + golangSastEnabled=data.get("golangSastEnabled", False), + javascriptSastEnabled=data.get("javascriptSastEnabled", False), + secretScanningEnabled=data.get("secretScanningEnabled", False), + trivyImageEnabled=data.get("trivyImageEnabled", False), + trivyDockerfileEnabled=data.get("trivyDockerfileEnabled", False), + socketScanningEnabled=data.get("socketScanningEnabled", False), + socketScaEnabled=data.get("socketScaEnabled", False), + additionalParameters=data.get("additionalParameters", ""), + ) + + +@dataclass +class SocketBasicsResponse: + """Data class representing the response from Socket Basics API calls.""" + success: bool + status: int + config: Optional[SocketBasicsConfig] = None + message: Optional[str] = None + + def __getitem__(self, key): + return getattr(self, key) + + def to_dict(self): + return asdict(self) + + @classmethod + def from_dict(cls, data: dict) -> "SocketBasicsResponse": + return cls( + config=SocketBasicsConfig.from_dict(data) if data else None, + success=True, + status=200, + ) + + +class Basics: + """ + Socket Basics API client for managing CI/CD security scanning configurations. + + Socket Basics is a security scanning suite that includes: + - SAST (Static Application Security Testing) for Python, Go, and JavaScript + - Secret scanning for hardcoded credentials + - Container security for Docker images and Dockerfiles + - Socket SCA dependency scanning + """ + + def __init__(self, api): + self.api = api + + def get_config( + self, org_slug: str, use_types: bool = False + ) -> Union[dict, SocketBasicsResponse]: + """ + Get Socket Basics configuration for an organization. + + Args: + org_slug: Organization slug + use_types: Whether to return typed response objects (default: False) + + Returns: + dict or SocketBasicsResponse: Configuration settings for Socket Basics + + Example: + >>> basics = socketdev_client.basics + >>> config = basics.get_config("my-org") + >>> print(config["pythonSastEnabled"]) + + >>> # Using typed response + >>> response = basics.get_config("my-org", use_types=True) + >>> print(response.config.pythonSastEnabled) + """ + path = f"orgs/{org_slug}/settings/socket-basics" + response = self.api.do_request(path=path, method="GET") + + if response.status_code == 200: + config_data = response.json() + if use_types: + return SocketBasicsResponse.from_dict(config_data) + return config_data + + error_message = response.json().get("error", {}).get("message", "Unknown error") + log.error(f"Failed to get Socket Basics configuration: {response.status_code}, message: {error_message}") + + if use_types: + return SocketBasicsResponse( + success=False, + status=response.status_code, + config=None, + message=error_message + ) + return {} \ No newline at end of file diff --git a/socketdev/core/dedupe.py b/socketdev/core/dedupe.py index 09a8ae7..b3b29da 100644 --- a/socketdev/core/dedupe.py +++ b/socketdev/core/dedupe.py @@ -13,7 +13,7 @@ def alert_key(alert: dict) -> tuple: return ( alert["type"], alert["severity"], - alert["category"], + alert.get("category"), Dedupe.normalize_file_path(alert.get("file")), alert.get("start"), alert.get("end") @@ -25,7 +25,7 @@ def alert_identity(alert: dict) -> tuple: return ( alert["type"], alert["severity"], - alert["category"], + alert.get("category"), Dedupe.normalize_file_path(alert.get("file")), alert.get("start"), alert.get("end") @@ -39,21 +39,29 @@ def alert_identity(alert: dict) -> tuple: for alert in pkg.get("alerts", []): identity = alert_identity(alert) - file = Dedupe.normalize_file_path(alert.get("file")) if identity not in alert_map: - alert_map[identity] = { + # Build alert dict with only fields that exist in the original alert + consolidated_alert = { "key": alert["key"], # keep the first key seen "type": alert["type"], "severity": alert["severity"], - "category": alert["category"], - "file": file, - "start": alert.get("start"), - "end": alert.get("end"), "releases": [release], "props": alert.get("props", []), "action": alert["action"] } + + # Only include optional fields if they exist in the original alert + if "category" in alert: + consolidated_alert["category"] = alert["category"] + if "file" in alert: + consolidated_alert["file"] = Dedupe.normalize_file_path(alert["file"]) + if "start" in alert: + consolidated_alert["start"] = alert["start"] + if "end" in alert: + consolidated_alert["end"] = alert["end"] + + alert_map[identity] = consolidated_alert else: if release not in alert_map[identity]["releases"]: alert_map[identity]["releases"].append(release) diff --git a/socketdev/version.py b/socketdev/version.py index c11769e..1adf1ce 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.0.7" +__version__ = "3.0.13"