From 0a7f7ad8537175c7726d030cf59b8a976ee609d4 Mon Sep 17 00:00:00 2001 From: Douglas Coburn Date: Thu, 2 Oct 2025 21:24:57 -0700 Subject: [PATCH 1/6] Fix dedupe logic to work with compact mode for the purl endpoint --- pyproject.toml | 2 +- socketdev/core/dedupe.py | 24 ++++++++++++++++-------- socketdev/version.py | 2 +- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1afc226..5bbdb6a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.0.6" +version = "3.0.7" requires-python = ">= 3.9" dependencies = [ 'requests', 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 6ed0182..c11769e 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.0.6" +__version__ = "3.0.7" From 808f31c276bef2862d5c3615016d46352b34117e Mon Sep 17 00:00:00 2001 From: Douglas Coburn Date: Mon, 13 Oct 2025 15:22:17 -0700 Subject: [PATCH 2/6] Add support for basics API and pinned workflow actions to commit hash --- .github/workflows/pr-preview.yml | 8 +- .github/workflows/release.yml | 6 +- .github/workflows/version-check.yml | 4 +- pyproject.toml | 2 +- socketdev/__init__.py | 2 + socketdev/basics/__init__.py | 120 ++++++++++++++++++++++++++ socketdev/basics/__pycache__/.gitkeep | 1 + socketdev/version.py | 2 +- 8 files changed, 134 insertions(+), 11 deletions(-) create mode 100644 socketdev/basics/__init__.py create mode 100644 socketdev/basics/__pycache__/.gitkeep diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index c4ed5bd..97e5d44 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -11,10 +11,10 @@ jobs: contents: read pull-requests: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: fetch-depth: 0 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: '3.x' @@ -57,14 +57,14 @@ jobs: - name: Publish to Test PyPI if: steps.version_check.outputs.exists != 'true' - uses: pypa/gh-action-pypi-publish@v1.12.4 + uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.4 with: repository-url: https://test.pypi.org/legacy/ verbose: true - name: Comment on PR if: steps.version_check.outputs.exists != 'true' - uses: actions/github-script@v7 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 env: VERSION: ${{ env.VERSION }} with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f1c7812..6fa4931 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,10 +10,10 @@ jobs: id-token: write contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: fetch-depth: 0 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 with: python-version: '3.x' @@ -54,7 +54,7 @@ jobs: - name: Publish to PyPI if: steps.version_check.outputs.pypi_exists != 'true' - uses: pypa/gh-action-pypi-publish@v1.12.4 + uses: pypa/gh-action-pypi-publish@67339c736fd9354cd4f8cb0b744f2b82a74b5c70 # v1.12.4 - name: Verify package is installable id: verify_package diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml index bd36127..fceeed2 100644 --- a/.github/workflows/version-check.yml +++ b/.github/workflows/version-check.yml @@ -11,7 +11,7 @@ jobs: check_version: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 with: fetch-depth: 0 # Fetch all history for all branches @@ -39,7 +39,7 @@ jobs: " - name: Manage PR Comment - uses: actions/github-script@v7 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 if: always() env: MAIN_VERSION: ${{ env.MAIN_VERSION }} diff --git a/pyproject.toml b/pyproject.toml index 5bbdb6a..c352676 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.8" 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/basics/__pycache__/.gitkeep b/socketdev/basics/__pycache__/.gitkeep new file mode 100644 index 0000000..06ac5c6 --- /dev/null +++ b/socketdev/basics/__pycache__/.gitkeep @@ -0,0 +1 @@ +# This directory is created for the basics module \ No newline at end of file diff --git a/socketdev/version.py b/socketdev/version.py index c11769e..35c154a 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.0.7" +__version__ = "3.0.8" From ab3c972f6d6982fd1b8dd07dcb81041e35bb159a Mon Sep 17 00:00:00 2001 From: Douglas Coburn Date: Mon, 13 Oct 2025 15:23:48 -0700 Subject: [PATCH 3/6] Removing unneeded files --- .gitignore | 3 ++- pyproject.toml | 2 +- socketdev/basics/__pycache__/.gitkeep | 1 - socketdev/version.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 socketdev/basics/__pycache__/.gitkeep 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/pyproject.toml b/pyproject.toml index c352676..081c15d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.0.8" +version = "3.0.9" requires-python = ">= 3.9" dependencies = [ 'requests', diff --git a/socketdev/basics/__pycache__/.gitkeep b/socketdev/basics/__pycache__/.gitkeep deleted file mode 100644 index 06ac5c6..0000000 --- a/socketdev/basics/__pycache__/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# This directory is created for the basics module \ No newline at end of file diff --git a/socketdev/version.py b/socketdev/version.py index 35c154a..67ae584 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.0.8" +__version__ = "3.0.9" From 9135c2067dbfa96ea2d68d80c102fe4cbc619fb8 Mon Sep 17 00:00:00 2001 From: Douglas Coburn Date: Tue, 14 Oct 2025 08:02:42 -0700 Subject: [PATCH 4/6] Fixes for basics endpoint --- README.rst | 40 ++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- socketdev/version.py | 2 +- 3 files changed, 42 insertions(+), 2 deletions(-) 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 081c15d..7fcb65c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.0.9" +version = "3.0.10" requires-python = ">= 3.9" dependencies = [ 'requests', diff --git a/socketdev/version.py b/socketdev/version.py index 67ae584..84994dc 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.0.9" +__version__ = "3.0.10" From eb070cf443af9a164cd567b6a7e32783895bda47 Mon Sep 17 00:00:00 2001 From: Douglas Coburn Date: Tue, 14 Oct 2025 08:27:06 -0700 Subject: [PATCH 5/6] Merging changed from main --- pyproject.toml | 2 +- socketdev/version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2d0823b..5156210 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.0.11" +version = "3.0.12" requires-python = ">= 3.9" dependencies = [ 'requests', diff --git a/socketdev/version.py b/socketdev/version.py index 6c5152b..730a6c4 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.0.11" +__version__ = "3.0.12" From ddebfacab2d4ca21a85a8f1e4b57af9fda2543d9 Mon Sep 17 00:00:00 2001 From: Douglas Coburn Date: Tue, 14 Oct 2025 08:39:12 -0700 Subject: [PATCH 6/6] Quoting url in workflow for github comment --- .github/workflows/pr-preview.yml | 2 +- pyproject.toml | 2 +- socketdev/version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/pyproject.toml b/pyproject.toml index 5156210..0339235 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.0.12" +version = "3.0.13" requires-python = ">= 3.9" dependencies = [ 'requests', diff --git a/socketdev/version.py b/socketdev/version.py index 730a6c4..1adf1ce 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.0.12" +__version__ = "3.0.13"