Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: SocketDev/socket-python-cli
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v2.0.43
Choose a base ref
...
head repository: SocketDev/socket-python-cli
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
  • 7 commits
  • 18 files changed
  • 1 contributor

Commits on Apr 14, 2025

  1. Fix Windows Path Normalization (#75)

    * Update version for deploy
    
    * Version updated
    
    * Stripping path from file name as well
    
    * Fixing workspace name
    
    * Removed redundant strip
    dacoburn authored Apr 14, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    96e6920 View commit details

Commits on Apr 15, 2025

  1. Doug/add plugins (#76)

    * version update
    
    * Finished adding Jira support
    
    * Updated the read me with Jira details
    dacoburn authored Apr 15, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    8cbf2da View commit details

Commits on Apr 16, 2025

  1. Added Slack plugin support (#77)

    dacoburn authored Apr 16, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    e75a1d7 View commit details
  2. Fixed slack import breaking bug (#78)

    dacoburn authored Apr 16, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    e8336f7 View commit details

Commits on Apr 21, 2025

  1. Doug/update comment template (#79)

    * Changed Dependency Overview to new template
    
    * Updated the Security Issue comment to the new template
    dacoburn authored Apr 21, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    4693beb View commit details
  2. Doug/add private flag for repo (#80)

    * Updated config params for support private/public repo setting
    
    * Added repo visibility to shared config
    dacoburn authored Apr 21, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    de2b480 View commit details

Commits on Apr 22, 2025

  1. Added support for exlcuded ecosystems (#81)

    dacoburn authored Apr 22, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    e250d14 View commit details
59 changes: 47 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -23,13 +23,14 @@ If you don't want to provide the Socket API Token every time then you can use th
| --api-token | False | | Socket Security API token (can also be set via SOCKET_SECURITY_API_KEY env var) |
#### Repository
| Parameter | Required | Default | Description |
|:--------------|:---------|:--------|:------------------------------------------------------------------------|
| --repo | False | | Repository name in owner/repo format |
| --integration | False | api | Integration type (api, github, gitlab) |
| --owner | False | | Name of the integration owner, defaults to the socket organization slug |
| --branch | False | "" | Branch name |
| --committers | False | | Committer(s) to filter by |
| Parameter | Required | Default | Description |
|:-----------------|:---------|:--------|:------------------------------------------------------------------------|
| --repo | False | | Repository name in owner/repo format |
| --integration | False | api | Integration type (api, github, gitlab) |
| --owner | False | | Name of the integration owner, defaults to the socket organization slug |
| --branch | False | "" | Branch name |
| --committers | False | | Committer(s) to filter by |
| --repo-is-public | False | False | If set, flags a new repository creation as public. Defaults to false. |
#### Pull Request and Commit
| Parameter | Required | Default | Description |
@@ -39,11 +40,12 @@ If you don't want to provide the Socket API Token every time then you can use th
| --commit-sha | False | "" | Commit SHA |
#### Path and File
| Parameter | Required | Default | Description |
|:--------------|:---------|:--------|:-------------------------------------|
| --target-path | False | ./ | Target path for analysis |
| --sbom-file | False | | SBOM file path |
| --files | False | [] | Files to analyze (JSON array string) |
| Parameter | Required | Default | Description |
|:-------------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| --target-path | False | ./ | Target path for analysis |
| --sbom-file | False | | SBOM file path |
| --files | False | [] | Files to analyze (JSON array string) |
| --exclude-patterns | False | [] | List of patterns to exclude from analysis (JSON array string). You can get supported files form the [Supported Files API](https://docs.socket.dev/reference/getsupportedfiles) |
#### Branch and Scan Configuration
| Parameter | Required | Default | Description |
@@ -76,6 +78,39 @@ If you don't want to provide the Socket API Token every time then you can use th
| --timeout | False | | Timeout in seconds for API requests |
| --include-module-folders | False | False | If enabled will include manifest files from folders like node_modules |

#### Plugins

The Python CLI currently Supports the following plugins:

- Jira
- Slack

##### Jira

| Environment Variable | Required | Default | Description |
|:------------------------|:---------|:--------|:-----------------------------------|
| SOCKET_JIRA_ENABLED | False | false | Enables/Disables the Jira Plugin |
| SOCKET_JIRA_CONFIG_JSON | True | None | Required if the Plugin is enabled. |

Example `SOCKET_JIRA_CONFIG_JSON` value

````json
{"url": "https://REPLACE_ME.atlassian.net", "email": "example@example.com", "api_token": "REPLACE_ME", "project": "REPLACE_ME" }
````

##### Slack

| Environment Variable | Required | Default | Description |
|:-------------------------|:---------|:--------|:-----------------------------------|
| SOCKET_SLACK_ENABLED | False | false | Enables/Disables the Slack Plugin |
| SOCKET_SLACK_CONFIG_JSON | True | None | Required if the Plugin is enabled. |

Example `SOCKET_SLACK_CONFIG_JSON` value

````json
{"url": "https://REPLACE_ME_WEBHOOK"}
````

## File Selection Behavior

The CLI determines which files to scan based on the following logic:
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"

[project]
name = "socketsecurity"
version = "2.0.43"
version = "2.0.56"
requires-python = ">= 3.10"
license = {"file" = "LICENSE"}
dependencies = [
2 changes: 1 addition & 1 deletion socketsecurity/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__author__ = 'socket.dev'
__version__ = '2.0.43'
__version__ = '2.0.56'
85 changes: 70 additions & 15 deletions socketsecurity/config.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
import argparse
import logging
import os
from dataclasses import asdict, dataclass
from dataclasses import asdict, dataclass, field
from typing import List, Optional
from socketsecurity import __version__
from socketdev import INTEGRATION_TYPES, IntegrationType
import json


def get_plugin_config_from_env(prefix: str) -> dict:
config_str = os.getenv(f"{prefix}_CONFIG_JSON", "{}")
try:
return json.loads(config_str)
except json.JSONDecodeError:
return {}

@dataclass
class PluginConfig:
enabled: bool = False
levels: List[str] = None
config: Optional[dict] = None


@dataclass
@@ -35,7 +51,12 @@ class CliConfig:
timeout: Optional[int] = 1200
exclude_license_details: bool = False
include_module_folders: bool = False
repo_is_public: bool = False
excluded_ecosystems: list[str] = field(default_factory=lambda: [])
version: str = __version__
jira_plugin: PluginConfig = field(default_factory=PluginConfig)
slack_plugin: PluginConfig = field(default_factory=PluginConfig)

@classmethod
def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
parser = create_argument_parser()
@@ -76,8 +97,27 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
'timeout': args.timeout,
'exclude_license_details': args.exclude_license_details,
'include_module_folders': args.include_module_folders,
'repo_is_public': args.repo_is_public,
"excluded_ecosystems": args.excluded_ecosystems,
'version': __version__
}
try:
config_args["excluded_ecosystems"] = json.loads(config_args["excluded_ecosystems"].replace("'", '"'))
except json.JSONDecodeError:
logging.error(f"Unable to parse excluded_ecosystems: {config_args['excluded_ecosystems']}")
exit(1)
config_args.update({
"jira_plugin": PluginConfig(
enabled=os.getenv("SOCKET_JIRA_ENABLED", "false").lower() == "true",
levels=os.getenv("SOCKET_JIRA_LEVELS", "block,warn").split(","),
config=get_plugin_config_from_env("SOCKET_JIRA")
),
"slack_plugin": PluginConfig(
enabled=os.getenv("SOCKET_SLACK_ENABLED", "false").lower() == "true",
levels=os.getenv("SOCKET_SLACK_LEVELS", "block,warn").split(","),
config=get_plugin_config_from_env("SOCKET_SLACK")
)
})

if args.owner:
config_args['integration_org_slug'] = args.owner
@@ -117,30 +157,32 @@ def create_argument_parser() -> argparse.ArgumentParser:
required=False
)
repo_group.add_argument(
"--repo-is-public",
dest="repo_is_public",
action="store_true",
help="If set it will flag a new repository creation as public. Defaults to false."
)
repo_group.add_argument(
"--branch",
metavar="<name>",
help="Branch name",
default=""
)

integration_group = parser.add_argument_group('Integration')
integration_group.add_argument(
"--integration",
choices=INTEGRATION_TYPES,
metavar="<type>",
help="Integration type",
help="Integration type of api, github, gitlab, azure, or bitbucket. Defaults to api",
default="api"
)
repo_group.add_argument(
integration_group.add_argument(
"--owner",
metavar="<name>",
help="Name of the integration owner, defaults to the socket organization slug",
required=False
)
repo_group.add_argument(
"--branch",
metavar="<name>",
help="Branch name",
default=""
)
repo_group.add_argument(
"--committers",
metavar="<name>",
help="Committer(s) to filter by",
nargs="*"
)

# Pull Request and Commit info
pr_group = parser.add_argument_group('Pull Request and Commit')
@@ -179,6 +221,12 @@ def create_argument_parser() -> argparse.ArgumentParser:
dest="commit_sha",
help=argparse.SUPPRESS
)
pr_group.add_argument(
"--committers",
metavar="<name>",
help="Committer for the commit (comma separated)",
nargs="*"
)

# Path and File options
path_group = parser.add_argument_group('Path and File')
@@ -212,6 +260,13 @@ def create_argument_parser() -> argparse.ArgumentParser:
help="Files to analyze (JSON array string)"
)

path_group.add_argument(
"--excluded-ecosystems",
default="[]",
dest="excluded_ecosystems",
help="List of ecosystems to exclude from analysis (JSON array string)"
)

# Branch and Scan Configuration
config_group = parser.add_argument_group('Branch and Scan Configuration')
config_group.add_argument(
39 changes: 25 additions & 14 deletions socketsecurity/core/__init__.py
Original file line number Diff line number Diff line change
@@ -21,7 +21,7 @@
FullScan,
Issue,
Package,
Purl,
Purl
)
from socketsecurity.core.exceptions import APIResourceNotFound
from socketsecurity.core.licenses import Licenses
@@ -184,6 +184,8 @@ def find_files(self, path: str) -> List[str]:
patterns = fallback_patterns

for ecosystem in patterns:
if ecosystem in self.config.excluded_ecosystems:
continue
ecosystem_patterns = patterns[ecosystem]
for file_name in ecosystem_patterns:
original_pattern = ecosystem_patterns[file_name]["pattern"]
@@ -201,7 +203,7 @@ def find_files(self, path: str) -> List[str]:

for glob_file in glob_files:
if os.path.isfile(glob_file) and not Core.is_excluded(glob_file, self.config.excluded_dirs):
files.add(glob_file)
files.add(glob_file.replace("\\", "/"))

glob_end = time.time()
log.debug(f"Globbing took {glob_end - glob_start:.4f} seconds")
@@ -290,12 +292,10 @@ def load_files_for_sending(files: List[str], workspace: str) -> List[Tuple[str,
[(field_name, (filename, file_object)), ...]
"""
send_files = []

if "\\" in workspace:
workspace = workspace.replace("\\", "/")
for file_path in files:
if "/" in file_path:
_, name = file_path.rsplit("/", 1)
else:
name = file_path
_, name = file_path.rsplit("/", 1)

if file_path.startswith(workspace):
key = file_path[len(workspace):]
@@ -306,7 +306,7 @@ def load_files_for_sending(files: List[str], workspace: str) -> List[Tuple[str,
key = key.lstrip("./")

f = open(file_path, 'rb')
payload = (key, (name, f))
payload = (key, (name.lstrip(workspace), f))
send_files.append(payload)

return send_files
@@ -441,7 +441,12 @@ def get_repo_info(self, repo_slug: str, default_branch: str = "socket-default-br
log.warning(f"Failed to get repository {repo_slug}, attempting to create it")
try:

create_response = self.sdk.repos.post(self.config.org_slug, name=repo_slug, default_branch=default_branch)
create_response = self.sdk.repos.post(
self.config.org_slug,
name=repo_slug,
default_branch=default_branch,
visibility=self.config.repo_visibility
)

# Check if the response is empty (failure) or has content (success)
if not create_response:
@@ -646,7 +651,7 @@ def create_diff_report(
seen_removed_packages = set()

for package_id, package in added_packages.items():
purl = Core.create_purl(package_id, added_packages)
purl = self.create_purl(package_id, added_packages)
base_purl = f"{purl.ecosystem}/{purl.name}@{purl.version}"

if (not direct_only or package.direct) and base_purl not in seen_new_packages:
@@ -660,7 +665,7 @@ def create_diff_report(
)

for package_id, package in removed_packages.items():
purl = Core.create_purl(package_id, removed_packages)
purl = self.create_purl(package_id, removed_packages)
base_purl = f"{purl.ecosystem}/{purl.name}@{purl.version}"

if (not direct_only or package.direct) and base_purl not in seen_removed_packages:
@@ -684,8 +689,13 @@ def create_diff_report(

return diff

@staticmethod
def create_purl(package_id: str, packages: dict[str, Package]) -> Purl:
def get_all_scores(self, packages: dict[str, Package]) -> dict[str, Package]:
components = []
for package_id in packages:
package = packages[package_id]
return packages

def create_purl(self, package_id: str, packages: dict[str, Package]) -> Purl:
"""
Creates the extended PURL data for package identification and tracking.
@@ -709,7 +719,8 @@ def create_purl(package_id: str, packages: dict[str, Package]) -> Purl:
size=package.size,
transitives=package.transitives,
url=package.url,
purl=package.purl
purl=package.purl,
scores=package.score
)
return purl

2 changes: 1 addition & 1 deletion socketsecurity/core/classes.py
Original file line number Diff line number Diff line change
@@ -370,7 +370,6 @@ def __init__(self, **kwargs):
def __str__(self):
return json.dumps(self.__dict__)


class Purl:
"""
Represents a Package URL (PURL) with extended metadata.
@@ -392,6 +391,7 @@ class Purl:
author_url: str
url: str
purl: str
scores: dict[str, int]

def __init__(self, **kwargs):
if kwargs:
Loading