From c81fe576ba1b10f1bd01141cd018e227a5e30fc9 Mon Sep 17 00:00:00 2001 From: lelia Date: Thu, 26 Feb 2026 19:53:07 -0500 Subject: [PATCH 1/5] Add support for SARIF file output Signed-off-by: lelia --- socketsecurity/config.py | 13 +++++++++++++ socketsecurity/output.py | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/socketsecurity/config.py b/socketsecurity/config.py index eb47772..40eb79c 100644 --- a/socketsecurity/config.py +++ b/socketsecurity/config.py @@ -40,6 +40,7 @@ class CliConfig: allow_unverified: bool = False enable_json: bool = False enable_sarif: bool = False + sarif_file: Optional[str] = None enable_gitlab_security: bool = False gitlab_security_file: Optional[str] = None disable_overview: bool = False @@ -103,6 +104,10 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig': args.api_token ) + # --sarif-file implies --enable-sarif + if args.sarif_file: + args.enable_sarif = True + # Strip quotes from commit message if present commit_message = args.commit_message if commit_message and commit_message.startswith('"') and commit_message.endswith('"'): @@ -126,6 +131,7 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig': 'allow_unverified': args.allow_unverified, 'enable_json': args.enable_json, 'enable_sarif': args.enable_sarif, + 'sarif_file': args.sarif_file, 'enable_gitlab_security': args.enable_gitlab_security, 'gitlab_security_file': args.gitlab_security_file, 'disable_overview': args.disable_overview, @@ -471,6 +477,13 @@ def create_argument_parser() -> argparse.ArgumentParser: action="store_true", help="Enable SARIF output of results instead of table or JSON format" ) + output_group.add_argument( + "--sarif-file", + dest="sarif_file", + metavar="", + default=None, + help="Output file path for SARIF report (implies --enable-sarif)" + ) output_group.add_argument( "--enable-gitlab-security", dest="enable_gitlab_security", diff --git a/socketsecurity/output.py b/socketsecurity/output.py index 478f2b2..69fae7e 100644 --- a/socketsecurity/output.py +++ b/socketsecurity/output.py @@ -139,6 +139,7 @@ def output_console_json(self, diff_report: Diff, sbom_file_name: Optional[str] = def output_console_sarif(self, diff_report: Diff, sbom_file_name: Optional[str] = None) -> None: """ Generate SARIF output from the diff report and print to console. + If --sarif-file is configured, also save to file. """ if diff_report.id != "NO_DIFF_RAN": # Generate the SARIF structure using Messages @@ -147,6 +148,14 @@ def output_console_sarif(self, diff_report: Diff, sbom_file_name: Optional[str] # Print the SARIF output to the console in JSON format print(json.dumps(console_security_comment, indent=2)) + # Save to file if --sarif-file is specified + if self.config.sarif_file: + sarif_path = Path(self.config.sarif_file) + sarif_path.parent.mkdir(parents=True, exist_ok=True) + with open(sarif_path, "w") as f: + json.dump(console_security_comment, f, indent=2) + self.logger.info(f"SARIF report saved to {self.config.sarif_file}") + def report_pass(self, diff_report: Diff) -> bool: """Determines if the report passes security checks""" # Priority 1: --disable-blocking always passes From 677b8518f478fefd794e0a955bc98d2d80a19a46 Mon Sep 17 00:00:00 2001 From: lelia Date: Thu, 26 Feb 2026 19:53:27 -0500 Subject: [PATCH 2/5] Ignore SARIF results Signed-off-by: lelia --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 06780f9..b742d8b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ run_container.sh bin scripts/*.py *.json +*.sarif !tests/**/*.json markdown_overview_temp.md markdown_security_temp.md From e329f32a82556e48126b203fd1b0f7c13d582161 Mon Sep 17 00:00:00 2001 From: lelia Date: Thu, 26 Feb 2026 19:55:35 -0500 Subject: [PATCH 3/5] Add test for new SARIF output functionality Signed-off-by: lelia --- tests/unit/test_output.py | 85 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_output.py b/tests/unit/test_output.py index 458714c..a7d99e9 100644 --- a/tests/unit/test_output.py +++ b/tests/unit/test_output.py @@ -156,4 +156,87 @@ def test_disable_blocking_overrides_strict_blocking(self): diff.unchanged_alerts = [Issue(error=True, warn=False)] # Should pass because disable_blocking takes precedence - assert handler.report_pass(diff) \ No newline at end of file + assert handler.report_pass(diff) + + def test_sarif_file_output(self, tmp_path): + """Test that --sarif-file writes SARIF report to a file""" + from socketsecurity.config import CliConfig + from unittest.mock import Mock + + sarif_path = tmp_path / "report.sarif" + + config = Mock(spec=CliConfig) + config.sarif_file = str(sarif_path) + config.sbom_file = None + + handler = OutputHandler(config, Mock()) + + diff = Diff() + diff.id = "test-scan-id" + diff.new_alerts = [Issue( + pkg_name="test-package", + pkg_version="1.0.0", + severity="high", + title="Test Vulnerability", + description="Test description", + type="malware", + url="https://socket.dev/test", + manifests="package.json", + pkg_type="npm", + key="test-key", + purl="pkg:npm/test-package@1.0.0", + error=True, + )] + + handler.output_console_sarif(diff) + + assert sarif_path.exists() + with open(sarif_path) as f: + sarif_data = json.load(f) + assert sarif_data["version"] == "2.1.0" + assert "$schema" in sarif_data + assert len(sarif_data["runs"]) == 1 + + def test_sarif_no_file_when_not_configured(self, tmp_path): + """Test that no file is written when --sarif-file is not set""" + from socketsecurity.config import CliConfig + from unittest.mock import Mock + + config = Mock(spec=CliConfig) + config.sarif_file = None + config.sbom_file = None + + handler = OutputHandler(config, Mock()) + + diff = Diff() + diff.id = "test-scan-id" + diff.new_alerts = [] + + handler.output_console_sarif(diff) + + # No files should be created in tmp_path + assert list(tmp_path.iterdir()) == [] + + def test_sarif_file_nested_directory(self, tmp_path): + """Test that --sarif-file creates parent directories if needed""" + from socketsecurity.config import CliConfig + from unittest.mock import Mock + + sarif_path = tmp_path / "nested" / "dir" / "report.sarif" + + config = Mock(spec=CliConfig) + config.sarif_file = str(sarif_path) + config.sbom_file = None + + handler = OutputHandler(config, Mock()) + + diff = Diff() + diff.id = "test-scan-id" + diff.new_alerts = [] + + handler.output_console_sarif(diff) + + assert sarif_path.exists() + with open(sarif_path) as f: + sarif_data = json.load(f) + assert sarif_data["version"] == "2.1.0" \ No newline at end of file From bf3a7cf2c6c52c45c769855c1645f8ef7408d238 Mon Sep 17 00:00:00 2001 From: lelia Date: Thu, 26 Feb 2026 19:56:01 -0500 Subject: [PATCH 4/5] Document new CLI output flag and clarify intended usage Signed-off-by: lelia --- README.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0a13219..01eb440 100644 --- a/README.md +++ b/README.md @@ -94,18 +94,27 @@ This will: - Save to `gl-dependency-scanning-report.json` - Include all actionable security alerts (error/warn level) +**Save SARIF report to file (e.g. for GitHub Code Scanning, SonarQube, or VS Code):** +```bash +socketcli --sarif-file results.sarif \ + --repo owner/repo \ + --target-path . +``` + **Multiple output formats:** ```bash socketcli --enable-json \ - --enable-sarif \ + --sarif-file results.sarif \ --enable-gitlab-security \ --repo owner/repo ``` This will simultaneously generate: - JSON output to console -- SARIF format to console -- GitLab Security Dashboard report to file +- SARIF report to `results.sarif` (and stdout) +- GitLab Security Dashboard report to `gl-dependency-scanning-report.json` + +> **Note:** `--enable-sarif` prints SARIF to stdout only. Use `--sarif-file ` to save to a file (this also implies `--enable-sarif`). These are independent from `--enable-gitlab-security`, which produces a separate GitLab-specific Dependency Scanning report. ### Requirements @@ -121,7 +130,7 @@ socketcli [-h] [--api-token API_TOKEN] [--repo REPO] [--workspace WORKSPACE] [-- [--target-path TARGET_PATH] [--sbom-file SBOM_FILE] [--license-file-name LICENSE_FILE_NAME] [--save-submitted-files-list SAVE_SUBMITTED_FILES_LIST] [--save-manifest-tar SAVE_MANIFEST_TAR] [--files FILES] [--sub-path SUB_PATH] [--workspace-name WORKSPACE_NAME] [--excluded-ecosystems EXCLUDED_ECOSYSTEMS] [--default-branch] [--pending-head] [--generate-license] [--enable-debug] - [--enable-json] [--enable-sarif] [--enable-gitlab-security] [--gitlab-security-file ] + [--enable-json] [--enable-sarif] [--sarif-file ] [--enable-gitlab-security] [--gitlab-security-file ] [--disable-overview] [--exclude-license-details] [--allow-unverified] [--disable-security-issue] [--ignore-commit-files] [--disable-blocking] [--enable-diff] [--scm SCM] [--timeout TIMEOUT] [--include-module-folders] [--reach] [--reach-version REACH_VERSION] [--reach-analysis-timeout REACH_ANALYSIS_TIMEOUT] @@ -189,7 +198,8 @@ If you don't want to provide the Socket API Token every time then you can use th | --generate-license | False | False | Generate license information | | --enable-debug | False | False | Enable debug logging | | --enable-json | False | False | Output in JSON format | -| --enable-sarif | False | False | Enable SARIF output of results instead of table or JSON format | +| --enable-sarif | False | False | Enable SARIF output of results instead of table or JSON format (prints to stdout) | +| --sarif-file | False | | Output file path for SARIF report (implies --enable-sarif). Use this to save SARIF output to a file for upload to GitHub Code Scanning, SonarQube, VS Code, or other SARIF-compatible tools | | --enable-gitlab-security | False | False | Enable GitLab Security Dashboard output format (Dependency Scanning report) | | --gitlab-security-file | False | gl-dependency-scanning-report.json | Output file path for GitLab Security report | | --disable-overview | False | False | Disable overview output | @@ -725,13 +735,13 @@ socketcli --enable-gitlab-security --gitlab-security-file custom-path.json GitLab security reports can be generated alongside other output formats: ```bash -socketcli --enable-json --enable-gitlab-security --enable-sarif +socketcli --enable-json --enable-gitlab-security --sarif-file results.sarif ``` This command will: - Output JSON format to console - Save GitLab Security Dashboard report to `gl-dependency-scanning-report.json` -- Save SARIF report (if configured) +- Save SARIF report to `results.sarif` ### Security Dashboard Features From fa64a8fd4bc74463d138bb00720b8e301891004b Mon Sep 17 00:00:00 2001 From: lelia Date: Thu, 26 Feb 2026 19:56:41 -0500 Subject: [PATCH 5/5] Bump version to prep for release Signed-off-by: lelia --- pyproject.toml | 2 +- socketsecurity/__init__.py | 2 +- uv.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1a59754..0ef61a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "socketsecurity" -version = "2.2.74" +version = "2.2.75" requires-python = ">= 3.10" license = {"file" = "LICENSE"} dependencies = [ diff --git a/socketsecurity/__init__.py b/socketsecurity/__init__.py index bee5bcf..440c08b 100644 --- a/socketsecurity/__init__.py +++ b/socketsecurity/__init__.py @@ -1,3 +1,3 @@ __author__ = 'socket.dev' -__version__ = '2.2.74' +__version__ = '2.2.75' USER_AGENT = f'SocketPythonCLI/{__version__}' diff --git a/uv.lock b/uv.lock index 229b89b..1fc4040 100644 --- a/uv.lock +++ b/uv.lock @@ -1263,7 +1263,7 @@ wheels = [ [[package]] name = "socketsecurity" -version = "2.2.73" +version = "2.2.74" source = { editable = "." } dependencies = [ { name = "bs4" },