Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -496,3 +496,34 @@ Implementation targets:
#### GitLab Integration
- `GITLAB_TOKEN`: GitLab API token for GitLab integration (supports both Bearer and PRIVATE-TOKEN authentication)
- `CI_JOB_TOKEN`: GitLab CI job token (automatically provided in GitLab CI environments)

### Manual Development Environment Setup

For manual setup without using the Make targets, follow these steps:

1. **Create a virtual environment:**
```bash
python -m venv .venv
```

2. **Activate the virtual environment:**
```bash
source .venv/bin/activate
```

3. **Sync dependencies with uv:**
```bash
uv sync
```

4. **Install pre-commit:**
```bash
uv add --dev pre-commit
```

5. **Register the pre-commit hook:**
```bash
pre-commit install
```

> **Note**: This manual setup is an alternative to the streamlined Make targets described above. For most development workflows, using `make first-time-setup` or `make first-time-local-setup` is recommended.
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ build-backend = "hatchling.build"

[project]
name = "socketsecurity"
version = "2.2.38"
version = "2.2.40"
requires-python = ">= 3.10"
license = {"file" = "LICENSE"}
dependencies = [
Expand All @@ -16,7 +16,7 @@ dependencies = [
'GitPython',
'packaging',
'python-dotenv',
'socketdev>=3.0.19,<4.0.0',
'socketdev>=3.0.21,<4.0.0',
"bs4>=0.0.2",
]
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion socketsecurity/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__author__ = 'socket.dev'
__version__ = '2.2.38'
__version__ = '2.2.40'
USER_AGENT = f'SocketPythonCLI/{__version__}'
91 changes: 82 additions & 9 deletions socketsecurity/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
from glob import glob
from io import BytesIO
from pathlib import PurePath
from typing import BinaryIO, Dict, List, Tuple, Set, Union
from typing import BinaryIO, Dict, List, Tuple, Set, Union, TYPE_CHECKING, Optional

if TYPE_CHECKING:
from socketsecurity.config import CliConfig
from socketdev import socketdev
from socketdev.exceptions import APIFailure
from socketdev.fullscans import FullScanParams, SocketArtifact
Expand Down Expand Up @@ -59,11 +62,13 @@ class Core:

config: SocketConfig
sdk: socketdev
cli_config: Optional['CliConfig']

def __init__(self, config: SocketConfig, sdk: socketdev) -> None:
def __init__(self, config: SocketConfig, sdk: socketdev, cli_config: Optional['CliConfig'] = None) -> None:
"""Initialize Core with configuration and SDK instance."""
self.config = config
self.sdk = sdk
self.cli_config = cli_config
self.set_org_vars()

def set_org_vars(self) -> None:
Expand Down Expand Up @@ -453,7 +458,61 @@ def empty_head_scan_file() -> List[str]:
log.debug(f"Created temporary empty file for baseline scan: {temp_path}")
return [temp_path]

def create_full_scan(self, files: List[str], params: FullScanParams, base_paths: List[str] = None) -> FullScan:
def finalize_tier1_scan(self, full_scan_id: str, facts_file_path: str) -> bool:
"""
Finalize a tier 1 reachability scan by associating it with a full scan.

This function reads the tier1ReachabilityScanId from the facts file and
calls the SDK to link it with the specified full scan.

Linking the tier 1 scan to the full scan helps the Socket team debug potential issues.

Args:
full_scan_id: The ID of the full scan to associate with the tier 1 scan
facts_file_path: Path to the .socket.facts.json file containing the tier1ReachabilityScanId

Returns:
True if successful, False otherwise
"""
log.debug(f"Finalizing tier 1 scan for full scan {full_scan_id}")

# Read the tier1ReachabilityScanId from the facts file
try:
if not os.path.exists(facts_file_path):
log.debug(f"Facts file not found: {facts_file_path}")
return False

with open(facts_file_path, 'r') as f:
facts = json.load(f)

tier1_scan_id = facts.get('tier1ReachabilityScanId')
if not tier1_scan_id:
log.debug(f"No tier1ReachabilityScanId found in {facts_file_path}")
return False

tier1_scan_id = tier1_scan_id.strip()
log.debug(f"Found tier1ReachabilityScanId: {tier1_scan_id}")

except (json.JSONDecodeError, IOError) as e:
log.debug(f"Failed to read tier1ReachabilityScanId from {facts_file_path}: {e}")
return False

# Call the SDK to finalize the tier 1 scan
try:
success = self.sdk.fullscans.finalize_tier1(
full_scan_id=full_scan_id,
tier1_reachability_scan_id=tier1_scan_id,
)

if success:
log.debug(f"Successfully finalized tier 1 scan {tier1_scan_id} for full scan {full_scan_id}")
return success

except Exception as e:
log.debug(f"Unable to finalize tier 1 scan: {e}")
return False

def create_full_scan(self, files: List[str], params: FullScanParams, base_paths: Optional[List[str]] = None) -> FullScan:
"""
Creates a new full scan via the Socket API.

Expand All @@ -478,16 +537,29 @@ def create_full_scan(self, files: List[str], params: FullScanParams, base_paths:
total_time = create_full_end - create_full_start
log.debug(f"New Full Scan created in {total_time:.2f} seconds")

# Finalize tier1 scan if reachability analysis was enabled
if self.cli_config and self.cli_config.reach:
facts_file_path = self.cli_config.reach_output_file or ".socket.facts.json"
log.debug(f"Reachability analysis enabled, finalizing tier1 scan for full scan {full_scan.id}")
try:
success = self.finalize_tier1_scan(full_scan.id, facts_file_path)
if success:
log.debug(f"Successfully finalized tier1 scan for full scan {full_scan.id}")
else:
log.debug(f"Failed to finalize tier1 scan for full scan {full_scan.id}")
except Exception as e:
log.warning(f"Error finalizing tier1 scan for full scan {full_scan.id}: {e}")

return full_scan

def create_full_scan_with_report_url(
self,
paths: List[str],
params: FullScanParams,
no_change: bool = False,
save_files_list_path: str = None,
save_manifest_tar_path: str = None,
base_paths: List[str] = None
save_files_list_path: Optional[str] = None,
save_manifest_tar_path: Optional[str] = None,
base_paths: Optional[List[str]] = None
) -> Diff:
"""Create a new full scan and return with html_report_url.

Expand Down Expand Up @@ -881,9 +953,9 @@ def create_new_diff(
paths: List[str],
params: FullScanParams,
no_change: bool = False,
save_files_list_path: str = None,
save_manifest_tar_path: str = None,
base_paths: List[str] = None
save_files_list_path: Optional[str] = None,
save_manifest_tar_path: Optional[str] = None,
base_paths: Optional[List[str]] = None
) -> Diff:
"""Create a new diff using the Socket SDK.

Expand Down Expand Up @@ -1130,6 +1202,7 @@ def create_purl(self, package_id: str, packages: dict[str, Package]) -> Purl:
)
return purl


@staticmethod
def get_source_data(package: Package, packages: dict) -> list:
"""
Expand Down
10 changes: 3 additions & 7 deletions socketsecurity/socketcli.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import os
import sys
import traceback
import shutil
Expand Down Expand Up @@ -81,7 +82,7 @@ def main_code():
client = CliClient(socket_config)
sdk.api.api_url = socket_config.api_url
log.debug("loaded client")
core = Core(socket_config, sdk)
core = Core(socket_config, sdk, config)
log.debug("loaded core")

# Check for required dependencies if reachability analysis is enabled
Expand Down Expand Up @@ -207,7 +208,6 @@ def main_code():
base_paths = [config.target_path] # Always use target_path as the single base path

if config.sub_paths:
import os
for sub_path in config.sub_paths:
full_scan_path = os.path.join(config.target_path, sub_path)
log.debug(f"Using sub-path for scanning: {full_scan_path}")
Expand Down Expand Up @@ -299,7 +299,6 @@ def main_code():

# If only-facts-file mode, mark the facts file for submission
if config.only_facts_file:
import os
facts_file_to_submit = os.path.abspath(output_path)
log.info(f"Only-facts-file mode: will submit only {facts_file_to_submit}")

Expand Down Expand Up @@ -355,9 +354,6 @@ def main_code():
# If using sub_paths, we need to check if manifest files exist in the scan paths
if config.sub_paths and not files_explicitly_specified:
# Override file checking to look in the scan paths instead
import os
from pathlib import Path

# Get manifest files from all scan paths
try:
all_scan_files = []
Expand Down Expand Up @@ -569,7 +565,7 @@ def main_code():
)
output_handler.handle_output(diff)

# Handle license generation
# Handle license generation
if not should_skip_scan and diff.id != "NO_DIFF_RAN" and diff.id != "NO_SCAN_RAN" and config.generate_license:
all_packages = {}
for purl in diff.packages:
Expand Down
Loading