Skip to content

Commit df6b52f

Browse files
authored
Fix dedupe logic to work with compact mode for the purl endpoint (#49)
* Fix dedupe logic to work with compact mode for the purl endpoint * Add support for basics API and pinned workflow actions to commit hash * Removing unneeded files * Fixes for basics endpoint * Merging changed from main * Quoting url in workflow for github comment
1 parent cfe41e9 commit df6b52f

File tree

8 files changed

+183
-12
lines changed

8 files changed

+183
-12
lines changed

.github/workflows/pr-preview.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ jobs:
118118
VERSION: ${{ env.VERSION }}
119119
run: |
120120
for i in {1..30}; do
121-
if pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple socketdev==${VERSION}; then
121+
if pip install --index-url 'https://test.pypi.org/simple/' --extra-index-url 'https://pypi.org/simple' socketdev==${VERSION}; then
122122
echo "Package ${VERSION} is now available and installable on Test PyPI"
123123
pip uninstall -y socketdev
124124
echo "success=true" >> $GITHUB_OUTPUT

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ dist
1515
*.dist
1616
*.egg-info
1717
*.cpython-312.pyc
18-
example-socket-export.py
18+
example-socket-export.py
19+
__pycache__/

README.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,46 @@ Get GitHub Flavored Markdown diff between two full scans.
304304
- **before (str)** - The base full scan ID
305305
- **after (str)** - The comparison full scan ID
306306

307+
basics.get_config(org_slug, use_types)
308+
""""""""""""""""""""""""""""""""""""""
309+
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.
310+
311+
**Usage:**
312+
313+
.. code-block:: python
314+
315+
from socketdev import socketdev
316+
socket = socketdev(token="REPLACE_ME")
317+
318+
# Basic usage - returns dictionary
319+
config = socket.basics.get_config("org_slug")
320+
print(f"Python SAST enabled: {config['pythonSastEnabled']}")
321+
print(f"Secret scanning enabled: {config['secretScanningEnabled']}")
322+
323+
# Using typed response objects
324+
from socketdev.basics import SocketBasicsConfig, SocketBasicsResponse
325+
response = socket.basics.get_config("org_slug", use_types=True)
326+
if response.success and response.config:
327+
print(f"JavaScript SAST: {response.config.javascriptSastEnabled}")
328+
print(f"Trivy scanning: {response.config.trivyImageEnabled}")
329+
330+
**PARAMETERS:**
331+
332+
- **org_slug (str)** - The organization name
333+
- **use_types (bool)** - Whether to return typed response objects (default: False)
334+
335+
**Socket Basics Features:**
336+
337+
- **Python SAST** - Static analysis for Python code
338+
- **Go SAST** - Static analysis for Go code
339+
- **JavaScript SAST** - Static analysis for JavaScript/TypeScript code
340+
- **Secret Scanning** - Detection of hardcoded secrets and credentials
341+
- **Trivy Image Scanning** - Vulnerability scanning for Docker images
342+
- **Trivy Dockerfile Scanning** - Vulnerability scanning for Dockerfiles
343+
- **Socket SCA** - Supply chain analysis for dependencies
344+
- **Socket Scanning** - General dependency security scanning
345+
- **Additional Parameters** - Custom configuration options
346+
307347
dependencies.get(limit, offset)
308348
"""""""""""""""""""""""""""""""
309349
Retrieve the dependencies for the organization associated with the API Key

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "socketdev"
7-
version = "3.0.7"
7+
version = "3.0.13"
88
requires-python = ">= 3.9"
99
dependencies = [
1010
'requests',

socketdev/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from socketdev.auditlog import AuditLog
2525
from socketdev.analytics import Analytics
2626
from socketdev.alerttypes import AlertTypes
27+
from socketdev.basics import Basics
2728
from socketdev.log import log
2829

2930
__author__ = "socket.dev"
@@ -72,6 +73,7 @@ def __init__(self, token: str, timeout: int = 1200):
7273
self.auditlog = AuditLog(self.api)
7374
self.analytics = Analytics(self.api)
7475
self.alerttypes = AlertTypes(self.api)
76+
self.basics = Basics(self.api)
7577

7678
@staticmethod
7779
def set_timeout(timeout: int):

socketdev/basics/__init__.py

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import logging
2+
from typing import Optional, Union
3+
from dataclasses import dataclass, asdict
4+
5+
log = logging.getLogger("socketdev")
6+
7+
8+
@dataclass
9+
class SocketBasicsConfig:
10+
"""Data class representing Socket Basics configuration settings."""
11+
pythonSastEnabled: bool = False
12+
golangSastEnabled: bool = False
13+
javascriptSastEnabled: bool = False
14+
secretScanningEnabled: bool = False
15+
trivyImageEnabled: bool = False
16+
trivyDockerfileEnabled: bool = False
17+
socketScanningEnabled: bool = False
18+
socketScaEnabled: bool = False
19+
additionalParameters: str = ""
20+
21+
def __getitem__(self, key):
22+
return getattr(self, key)
23+
24+
def to_dict(self):
25+
return asdict(self)
26+
27+
@classmethod
28+
def from_dict(cls, data: dict) -> "SocketBasicsConfig":
29+
return cls(
30+
pythonSastEnabled=data.get("pythonSastEnabled", False),
31+
golangSastEnabled=data.get("golangSastEnabled", False),
32+
javascriptSastEnabled=data.get("javascriptSastEnabled", False),
33+
secretScanningEnabled=data.get("secretScanningEnabled", False),
34+
trivyImageEnabled=data.get("trivyImageEnabled", False),
35+
trivyDockerfileEnabled=data.get("trivyDockerfileEnabled", False),
36+
socketScanningEnabled=data.get("socketScanningEnabled", False),
37+
socketScaEnabled=data.get("socketScaEnabled", False),
38+
additionalParameters=data.get("additionalParameters", ""),
39+
)
40+
41+
42+
@dataclass
43+
class SocketBasicsResponse:
44+
"""Data class representing the response from Socket Basics API calls."""
45+
success: bool
46+
status: int
47+
config: Optional[SocketBasicsConfig] = None
48+
message: Optional[str] = None
49+
50+
def __getitem__(self, key):
51+
return getattr(self, key)
52+
53+
def to_dict(self):
54+
return asdict(self)
55+
56+
@classmethod
57+
def from_dict(cls, data: dict) -> "SocketBasicsResponse":
58+
return cls(
59+
config=SocketBasicsConfig.from_dict(data) if data else None,
60+
success=True,
61+
status=200,
62+
)
63+
64+
65+
class Basics:
66+
"""
67+
Socket Basics API client for managing CI/CD security scanning configurations.
68+
69+
Socket Basics is a security scanning suite that includes:
70+
- SAST (Static Application Security Testing) for Python, Go, and JavaScript
71+
- Secret scanning for hardcoded credentials
72+
- Container security for Docker images and Dockerfiles
73+
- Socket SCA dependency scanning
74+
"""
75+
76+
def __init__(self, api):
77+
self.api = api
78+
79+
def get_config(
80+
self, org_slug: str, use_types: bool = False
81+
) -> Union[dict, SocketBasicsResponse]:
82+
"""
83+
Get Socket Basics configuration for an organization.
84+
85+
Args:
86+
org_slug: Organization slug
87+
use_types: Whether to return typed response objects (default: False)
88+
89+
Returns:
90+
dict or SocketBasicsResponse: Configuration settings for Socket Basics
91+
92+
Example:
93+
>>> basics = socketdev_client.basics
94+
>>> config = basics.get_config("my-org")
95+
>>> print(config["pythonSastEnabled"])
96+
97+
>>> # Using typed response
98+
>>> response = basics.get_config("my-org", use_types=True)
99+
>>> print(response.config.pythonSastEnabled)
100+
"""
101+
path = f"orgs/{org_slug}/settings/socket-basics"
102+
response = self.api.do_request(path=path, method="GET")
103+
104+
if response.status_code == 200:
105+
config_data = response.json()
106+
if use_types:
107+
return SocketBasicsResponse.from_dict(config_data)
108+
return config_data
109+
110+
error_message = response.json().get("error", {}).get("message", "Unknown error")
111+
log.error(f"Failed to get Socket Basics configuration: {response.status_code}, message: {error_message}")
112+
113+
if use_types:
114+
return SocketBasicsResponse(
115+
success=False,
116+
status=response.status_code,
117+
config=None,
118+
message=error_message
119+
)
120+
return {}

socketdev/core/dedupe.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def alert_key(alert: dict) -> tuple:
1313
return (
1414
alert["type"],
1515
alert["severity"],
16-
alert["category"],
16+
alert.get("category"),
1717
Dedupe.normalize_file_path(alert.get("file")),
1818
alert.get("start"),
1919
alert.get("end")
@@ -25,7 +25,7 @@ def alert_identity(alert: dict) -> tuple:
2525
return (
2626
alert["type"],
2727
alert["severity"],
28-
alert["category"],
28+
alert.get("category"),
2929
Dedupe.normalize_file_path(alert.get("file")),
3030
alert.get("start"),
3131
alert.get("end")
@@ -39,21 +39,29 @@ def alert_identity(alert: dict) -> tuple:
3939

4040
for alert in pkg.get("alerts", []):
4141
identity = alert_identity(alert)
42-
file = Dedupe.normalize_file_path(alert.get("file"))
4342

4443
if identity not in alert_map:
45-
alert_map[identity] = {
44+
# Build alert dict with only fields that exist in the original alert
45+
consolidated_alert = {
4646
"key": alert["key"], # keep the first key seen
4747
"type": alert["type"],
4848
"severity": alert["severity"],
49-
"category": alert["category"],
50-
"file": file,
51-
"start": alert.get("start"),
52-
"end": alert.get("end"),
5349
"releases": [release],
5450
"props": alert.get("props", []),
5551
"action": alert["action"]
5652
}
53+
54+
# Only include optional fields if they exist in the original alert
55+
if "category" in alert:
56+
consolidated_alert["category"] = alert["category"]
57+
if "file" in alert:
58+
consolidated_alert["file"] = Dedupe.normalize_file_path(alert["file"])
59+
if "start" in alert:
60+
consolidated_alert["start"] = alert["start"]
61+
if "end" in alert:
62+
consolidated_alert["end"] = alert["end"]
63+
64+
alert_map[identity] = consolidated_alert
5765
else:
5866
if release not in alert_map[identity]["releases"]:
5967
alert_map[identity]["releases"].append(release)

socketdev/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "3.0.7"
1+
__version__ = "3.0.13"

0 commit comments

Comments
 (0)