socketsecurity 2.2.75__tar.gz → 2.2.77__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/.github/workflows/e2e-test.yml +86 -0
  2. socketsecurity-2.2.77/.github/workflows/python-tests.yml +52 -0
  3. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/.gitignore +1 -0
  4. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/PKG-INFO +19 -8
  5. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/README.md +18 -7
  6. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/pyproject.toml +1 -1
  7. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/__init__.py +1 -1
  8. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/config.py +26 -0
  9. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/__init__.py +10 -13
  10. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/classes.py +44 -9
  11. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/output.py +32 -0
  12. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/plugins/slack.py +24 -14
  13. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/core/conftest.py +11 -4
  14. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/core/test_diff_generation.py +8 -24
  15. socketsecurity-2.2.77/tests/core/test_has_manifest_files.py +68 -0
  16. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/core/test_package_and_alerts.py +44 -14
  17. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/core/test_sdk_methods.py +25 -19
  18. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/core/test_supporting_methods.py +30 -20
  19. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/data/repos/repo_info_no_head.json +2 -1
  20. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/data/repos/repo_info_success.json +2 -1
  21. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/unit/test_config.py +24 -0
  22. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/unit/test_gitlab_auth_fallback.py +2 -0
  23. socketsecurity-2.2.77/tests/unit/test_output.py +338 -0
  24. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/uv.lock +5 -5
  25. socketsecurity-2.2.75/tests/unit/test_output.py +0 -159
  26. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/.github/CODEOWNERS +0 -0
  27. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
  28. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
  29. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
  30. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  31. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/.github/workflows/docker-stable.yml +0 -0
  32. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/.github/workflows/pr-preview.yml +0 -0
  33. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/.github/workflows/release.yml +0 -0
  34. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/.github/workflows/version-check.yml +0 -0
  35. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/.hooks/sync_version.py +0 -0
  36. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/.pre-commit-config.yaml +0 -0
  37. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/.python-version +0 -0
  38. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/CHANGELOG.md +0 -0
  39. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/Dockerfile +0 -0
  40. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/LICENSE +0 -0
  41. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/Makefile +0 -0
  42. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/docs/README.md +0 -0
  43. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/instructions/gitlab-commit-status/uat.md +0 -0
  44. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/pytest.ini +0 -0
  45. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/scripts/build_container.sh +0 -0
  46. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/scripts/build_container_flexible.sh +0 -0
  47. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/scripts/deploy-test-docker.sh +0 -0
  48. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/scripts/deploy-test-pypi.sh +0 -0
  49. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/scripts/docker-entrypoint.sh +0 -0
  50. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/scripts/run.sh +0 -0
  51. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/session.md +0 -0
  52. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socket.yml +0 -0
  53. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/cli_client.py +0 -0
  54. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/exceptions.py +0 -0
  55. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/git_interface.py +0 -0
  56. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/helper/__init__.py +0 -0
  57. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/helper/socket_facts_loader.py +0 -0
  58. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/lazy_file_loader.py +0 -0
  59. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/logging.py +0 -0
  60. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/messages.py +0 -0
  61. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/resource_utils.py +0 -0
  62. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/scm/__init__.py +0 -0
  63. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/scm/base.py +0 -0
  64. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/scm/client.py +0 -0
  65. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/scm/github.py +0 -0
  66. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/scm/gitlab.py +0 -0
  67. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/scm_comments.py +0 -0
  68. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/socket_config.py +0 -0
  69. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/tools/reachability.py +0 -0
  70. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/core/utils.py +0 -0
  71. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/plugins/__init__.py +0 -0
  72. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/plugins/base.py +0 -0
  73. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/plugins/formatters/__init__.py +0 -0
  74. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/plugins/formatters/slack.py +0 -0
  75. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/plugins/jira.py +0 -0
  76. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/plugins/manager.py +0 -0
  77. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/plugins/teams.py +0 -0
  78. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/plugins/webhook.py +0 -0
  79. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/socketsecurity/socketcli.py +0 -0
  80. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/__init__.py +0 -0
  81. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/core/create_diff_input.json +0 -0
  82. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/core/test_diff_alerts.py +0 -0
  83. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/data/fullscans/create_response.json +0 -0
  84. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/data/fullscans/diff/stream_diff.json +0 -0
  85. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
  86. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/data/fullscans/head_scan/metadata.json +0 -0
  87. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
  88. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
  89. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/data/fullscans/new_scan/metadata.json +0 -0
  90. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
  91. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/data/repos/repo_info_error.json +0 -0
  92. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/data/settings/security-policy.json +0 -0
  93. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/e2e/fixtures/simple-npm/index.js +0 -0
  94. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/e2e/fixtures/simple-npm/package.json +0 -0
  95. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/unit/__init__.py +0 -0
  96. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/unit/test_cli_config.py +0 -0
  97. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/unit/test_client.py +0 -0
  98. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/unit/test_gitlab_auth.py +0 -0
  99. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/unit/test_gitlab_commit_status.py +0 -0
  100. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/tests/unit/test_gitlab_format.py +0 -0
  101. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/workflows/bitbucket-pipelines.yml +0 -0
  102. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/workflows/github-actions.yml +0 -0
  103. {socketsecurity-2.2.75 → socketsecurity-2.2.77}/workflows/gitlab-ci.yml +0 -0
@@ -47,6 +47,54 @@ jobs:
47
47
  exit 1
48
48
  fi
49
49
 
50
+ e2e-sarif:
51
+ runs-on: ubuntu-latest
52
+ steps:
53
+ - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871
54
+ with:
55
+ fetch-depth: 0
56
+
57
+ - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3
58
+ with:
59
+ python-version: '3.12'
60
+
61
+ - name: Install CLI from local repo
62
+ run: |
63
+ python -m pip install --upgrade pip
64
+ pip install .
65
+
66
+ - name: Verify --sarif-reachable-only without --reach exits non-zero
67
+ run: |
68
+ if socketcli --sarif-reachable-only --api-token dummy 2>&1; then
69
+ echo "FAIL: Expected non-zero exit"
70
+ exit 1
71
+ else
72
+ echo "PASS: Exited non-zero as expected"
73
+ fi
74
+
75
+ - name: Run Socket CLI scan with --sarif-file
76
+ env:
77
+ SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_CLI_API_TOKEN }}
78
+ run: |
79
+ set -o pipefail
80
+ socketcli \
81
+ --target-path tests/e2e/fixtures/simple-npm \
82
+ --sarif-file /tmp/results.sarif \
83
+ --disable-blocking \
84
+ 2>&1 | tee /tmp/sarif-output.log
85
+
86
+ - name: Verify SARIF file is valid
87
+ run: |
88
+ python3 -c "
89
+ import json, sys
90
+ with open('/tmp/results.sarif') as f:
91
+ data = json.load(f)
92
+ assert data['version'] == '2.1.0', f'Invalid version: {data[\"version\"]}'
93
+ assert '\$schema' in data, 'Missing \$schema'
94
+ count = len(data['runs'][0]['results'])
95
+ print(f'PASS: Valid SARIF 2.1.0 with {count} result(s)')
96
+ "
97
+
50
98
  e2e-reachability:
51
99
  runs-on: ubuntu-latest
52
100
  steps:
@@ -107,3 +155,41 @@ jobs:
107
155
  cat /tmp/reach-output.log
108
156
  exit 1
109
157
  fi
158
+
159
+ - name: Run scan with --sarif-file (all results)
160
+ env:
161
+ SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_CLI_API_TOKEN }}
162
+ run: |
163
+ socketcli \
164
+ --target-path tests/e2e/fixtures/simple-npm \
165
+ --reach \
166
+ --sarif-file /tmp/sarif-all.sarif \
167
+ --disable-blocking \
168
+ 2>/dev/null || true
169
+
170
+ - name: Run scan with --sarif-file --sarif-reachable-only (filtered results)
171
+ env:
172
+ SOCKET_SECURITY_API_KEY: ${{ secrets.SOCKET_CLI_API_TOKEN }}
173
+ run: |
174
+ socketcli \
175
+ --target-path tests/e2e/fixtures/simple-npm \
176
+ --reach \
177
+ --sarif-file /tmp/sarif-reachable.sarif \
178
+ --sarif-reachable-only \
179
+ --disable-blocking \
180
+ 2>/dev/null || true
181
+
182
+ - name: Verify reachable-only results are a subset of all results
183
+ run: |
184
+ python3 -c "
185
+ import json
186
+ with open('/tmp/sarif-all.sarif') as f:
187
+ all_data = json.load(f)
188
+ with open('/tmp/sarif-reachable.sarif') as f:
189
+ reach_data = json.load(f)
190
+ all_count = len(all_data['runs'][0]['results'])
191
+ reach_count = len(reach_data['runs'][0]['results'])
192
+ print(f'All results: {all_count}, Reachable-only results: {reach_count}')
193
+ assert reach_count <= all_count, f'FAIL: reachable ({reach_count}) > all ({all_count})'
194
+ print('PASS: Reachable-only results is a subset of all results')
195
+ "
@@ -0,0 +1,52 @@
1
+ name: Unit Tests
2
+
3
+ env:
4
+ PYTHON_VERSION: "3.12"
5
+
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ paths:
10
+ - "socketsecurity/**/*.py"
11
+ - "tests/unit/**/*.py"
12
+ - "tests/core/**/*.py"
13
+ - "pyproject.toml"
14
+ - "uv.lock"
15
+ - ".github/workflows/python-tests.yml"
16
+ pull_request:
17
+ paths:
18
+ - "socketsecurity/**/*.py"
19
+ - "tests/unit/**/*.py"
20
+ - "tests/core/**/*.py"
21
+ - "pyproject.toml"
22
+ - "uv.lock"
23
+ - ".github/workflows/python-tests.yml"
24
+ workflow_dispatch:
25
+
26
+ permissions:
27
+ contents: read
28
+
29
+ concurrency:
30
+ group: python-tests-${{ github.ref }}
31
+ cancel-in-progress: true
32
+
33
+ jobs:
34
+ python-tests:
35
+ runs-on: ubuntu-latest
36
+ timeout-minutes: 20
37
+ steps:
38
+ - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871
39
+ with:
40
+ fetch-depth: 1
41
+ persist-credentials: false
42
+ - name: 🐍 setup python
43
+ uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3
44
+ with:
45
+ python-version: ${{ env.PYTHON_VERSION }}
46
+ - name: 🛠️ install deps
47
+ run: |
48
+ python -m pip install --upgrade pip
49
+ pip install uv
50
+ uv sync --extra test
51
+ - name: 🧪 run tests
52
+ run: uv run pytest -q tests/unit/ tests/core/
@@ -13,6 +13,7 @@ run_container.sh
13
13
  bin
14
14
  scripts/*.py
15
15
  *.json
16
+ *.sarif
16
17
  !tests/**/*.json
17
18
  markdown_overview_temp.md
18
19
  markdown_security_temp.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: socketsecurity
3
- Version: 2.2.75
3
+ Version: 2.2.77
4
4
  Summary: Socket Security CLI for CI/CD
5
5
  Project-URL: Homepage, https://socket.dev
6
6
  Author-email: Douglas Coburn <douglas@socket.dev>
@@ -152,18 +152,27 @@ This will:
152
152
  - Save to `gl-dependency-scanning-report.json`
153
153
  - Include all actionable security alerts (error/warn level)
154
154
 
155
+ **Save SARIF report to file (e.g. for GitHub Code Scanning, SonarQube, or VS Code):**
156
+ ```bash
157
+ socketcli --sarif-file results.sarif \
158
+ --repo owner/repo \
159
+ --target-path .
160
+ ```
161
+
155
162
  **Multiple output formats:**
156
163
  ```bash
157
164
  socketcli --enable-json \
158
- --enable-sarif \
165
+ --sarif-file results.sarif \
159
166
  --enable-gitlab-security \
160
167
  --repo owner/repo
161
168
  ```
162
169
 
163
170
  This will simultaneously generate:
164
171
  - JSON output to console
165
- - SARIF format to console
166
- - GitLab Security Dashboard report to file
172
+ - SARIF report to `results.sarif` (and stdout)
173
+ - GitLab Security Dashboard report to `gl-dependency-scanning-report.json`
174
+
175
+ > **Note:** `--enable-sarif` prints SARIF to stdout only. Use `--sarif-file <path>` to save to a file (this also implies `--enable-sarif`). Add `--sarif-reachable-only` (requires `--reach`) to filter results down to only reachable findings — useful for uploading to GitHub Code Scanning without noisy alerts on unreachable vulns. These flags are independent from `--enable-gitlab-security`, which produces a separate GitLab-specific Dependency Scanning report.
167
176
 
168
177
  ### Requirements
169
178
 
@@ -179,7 +188,7 @@ socketcli [-h] [--api-token API_TOKEN] [--repo REPO] [--workspace WORKSPACE] [--
179
188
  [--target-path TARGET_PATH] [--sbom-file SBOM_FILE] [--license-file-name LICENSE_FILE_NAME] [--save-submitted-files-list SAVE_SUBMITTED_FILES_LIST]
180
189
  [--save-manifest-tar SAVE_MANIFEST_TAR] [--files FILES] [--sub-path SUB_PATH] [--workspace-name WORKSPACE_NAME]
181
190
  [--excluded-ecosystems EXCLUDED_ECOSYSTEMS] [--default-branch] [--pending-head] [--generate-license] [--enable-debug]
182
- [--enable-json] [--enable-sarif] [--enable-gitlab-security] [--gitlab-security-file <path>]
191
+ [--enable-json] [--enable-sarif] [--sarif-file <path>] [--sarif-reachable-only] [--enable-gitlab-security] [--gitlab-security-file <path>]
183
192
  [--disable-overview] [--exclude-license-details] [--allow-unverified] [--disable-security-issue]
184
193
  [--ignore-commit-files] [--disable-blocking] [--enable-diff] [--scm SCM] [--timeout TIMEOUT] [--include-module-folders]
185
194
  [--reach] [--reach-version REACH_VERSION] [--reach-analysis-timeout REACH_ANALYSIS_TIMEOUT]
@@ -247,7 +256,9 @@ If you don't want to provide the Socket API Token every time then you can use th
247
256
  | --generate-license | False | False | Generate license information |
248
257
  | --enable-debug | False | False | Enable debug logging |
249
258
  | --enable-json | False | False | Output in JSON format |
250
- | --enable-sarif | False | False | Enable SARIF output of results instead of table or JSON format |
259
+ | --enable-sarif | False | False | Enable SARIF output of results instead of table or JSON format (prints to stdout) |
260
+ | --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 |
261
+ | --sarif-reachable-only | False | False | Filter SARIF output to only include reachable findings (requires --reach) |
251
262
  | --enable-gitlab-security | False | False | Enable GitLab Security Dashboard output format (Dependency Scanning report) |
252
263
  | --gitlab-security-file | False | gl-dependency-scanning-report.json | Output file path for GitLab Security report |
253
264
  | --disable-overview | False | False | Disable overview output |
@@ -783,13 +794,13 @@ socketcli --enable-gitlab-security --gitlab-security-file custom-path.json
783
794
  GitLab security reports can be generated alongside other output formats:
784
795
 
785
796
  ```bash
786
- socketcli --enable-json --enable-gitlab-security --enable-sarif
797
+ socketcli --enable-json --enable-gitlab-security --sarif-file results.sarif
787
798
  ```
788
799
 
789
800
  This command will:
790
801
  - Output JSON format to console
791
802
  - Save GitLab Security Dashboard report to `gl-dependency-scanning-report.json`
792
- - Save SARIF report (if configured)
803
+ - Save SARIF report to `results.sarif`
793
804
 
794
805
  ### Security Dashboard Features
795
806
 
@@ -94,18 +94,27 @@ This will:
94
94
  - Save to `gl-dependency-scanning-report.json`
95
95
  - Include all actionable security alerts (error/warn level)
96
96
 
97
+ **Save SARIF report to file (e.g. for GitHub Code Scanning, SonarQube, or VS Code):**
98
+ ```bash
99
+ socketcli --sarif-file results.sarif \
100
+ --repo owner/repo \
101
+ --target-path .
102
+ ```
103
+
97
104
  **Multiple output formats:**
98
105
  ```bash
99
106
  socketcli --enable-json \
100
- --enable-sarif \
107
+ --sarif-file results.sarif \
101
108
  --enable-gitlab-security \
102
109
  --repo owner/repo
103
110
  ```
104
111
 
105
112
  This will simultaneously generate:
106
113
  - JSON output to console
107
- - SARIF format to console
108
- - GitLab Security Dashboard report to file
114
+ - SARIF report to `results.sarif` (and stdout)
115
+ - GitLab Security Dashboard report to `gl-dependency-scanning-report.json`
116
+
117
+ > **Note:** `--enable-sarif` prints SARIF to stdout only. Use `--sarif-file <path>` to save to a file (this also implies `--enable-sarif`). Add `--sarif-reachable-only` (requires `--reach`) to filter results down to only reachable findings — useful for uploading to GitHub Code Scanning without noisy alerts on unreachable vulns. These flags are independent from `--enable-gitlab-security`, which produces a separate GitLab-specific Dependency Scanning report.
109
118
 
110
119
  ### Requirements
111
120
 
@@ -121,7 +130,7 @@ socketcli [-h] [--api-token API_TOKEN] [--repo REPO] [--workspace WORKSPACE] [--
121
130
  [--target-path TARGET_PATH] [--sbom-file SBOM_FILE] [--license-file-name LICENSE_FILE_NAME] [--save-submitted-files-list SAVE_SUBMITTED_FILES_LIST]
122
131
  [--save-manifest-tar SAVE_MANIFEST_TAR] [--files FILES] [--sub-path SUB_PATH] [--workspace-name WORKSPACE_NAME]
123
132
  [--excluded-ecosystems EXCLUDED_ECOSYSTEMS] [--default-branch] [--pending-head] [--generate-license] [--enable-debug]
124
- [--enable-json] [--enable-sarif] [--enable-gitlab-security] [--gitlab-security-file <path>]
133
+ [--enable-json] [--enable-sarif] [--sarif-file <path>] [--sarif-reachable-only] [--enable-gitlab-security] [--gitlab-security-file <path>]
125
134
  [--disable-overview] [--exclude-license-details] [--allow-unverified] [--disable-security-issue]
126
135
  [--ignore-commit-files] [--disable-blocking] [--enable-diff] [--scm SCM] [--timeout TIMEOUT] [--include-module-folders]
127
136
  [--reach] [--reach-version REACH_VERSION] [--reach-analysis-timeout REACH_ANALYSIS_TIMEOUT]
@@ -189,7 +198,9 @@ If you don't want to provide the Socket API Token every time then you can use th
189
198
  | --generate-license | False | False | Generate license information |
190
199
  | --enable-debug | False | False | Enable debug logging |
191
200
  | --enable-json | False | False | Output in JSON format |
192
- | --enable-sarif | False | False | Enable SARIF output of results instead of table or JSON format |
201
+ | --enable-sarif | False | False | Enable SARIF output of results instead of table or JSON format (prints to stdout) |
202
+ | --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 |
203
+ | --sarif-reachable-only | False | False | Filter SARIF output to only include reachable findings (requires --reach) |
193
204
  | --enable-gitlab-security | False | False | Enable GitLab Security Dashboard output format (Dependency Scanning report) |
194
205
  | --gitlab-security-file | False | gl-dependency-scanning-report.json | Output file path for GitLab Security report |
195
206
  | --disable-overview | False | False | Disable overview output |
@@ -725,13 +736,13 @@ socketcli --enable-gitlab-security --gitlab-security-file custom-path.json
725
736
  GitLab security reports can be generated alongside other output formats:
726
737
 
727
738
  ```bash
728
- socketcli --enable-json --enable-gitlab-security --enable-sarif
739
+ socketcli --enable-json --enable-gitlab-security --sarif-file results.sarif
729
740
  ```
730
741
 
731
742
  This command will:
732
743
  - Output JSON format to console
733
744
  - Save GitLab Security Dashboard report to `gl-dependency-scanning-report.json`
734
- - Save SARIF report (if configured)
745
+ - Save SARIF report to `results.sarif`
735
746
 
736
747
  ### Security Dashboard Features
737
748
 
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "socketsecurity"
9
- version = "2.2.75"
9
+ version = "2.2.77"
10
10
  requires-python = ">= 3.10"
11
11
  license = {"file" = "LICENSE"}
12
12
  dependencies = [
@@ -1,3 +1,3 @@
1
1
  __author__ = 'socket.dev'
2
- __version__ = '2.2.75'
2
+ __version__ = '2.2.77'
3
3
  USER_AGENT = f'SocketPythonCLI/{__version__}'
@@ -40,6 +40,8 @@ class CliConfig:
40
40
  allow_unverified: bool = False
41
41
  enable_json: bool = False
42
42
  enable_sarif: bool = False
43
+ sarif_file: Optional[str] = None
44
+ sarif_reachable_only: bool = False
43
45
  enable_gitlab_security: bool = False
44
46
  gitlab_security_file: Optional[str] = None
45
47
  disable_overview: bool = False
@@ -103,6 +105,10 @@ class CliConfig:
103
105
  args.api_token
104
106
  )
105
107
 
108
+ # --sarif-file implies --enable-sarif
109
+ if args.sarif_file:
110
+ args.enable_sarif = True
111
+
106
112
  # Strip quotes from commit message if present
107
113
  commit_message = args.commit_message
108
114
  if commit_message and commit_message.startswith('"') and commit_message.endswith('"'):
@@ -126,6 +132,8 @@ class CliConfig:
126
132
  'allow_unverified': args.allow_unverified,
127
133
  'enable_json': args.enable_json,
128
134
  'enable_sarif': args.enable_sarif,
135
+ 'sarif_file': args.sarif_file,
136
+ 'sarif_reachable_only': args.sarif_reachable_only,
129
137
  'enable_gitlab_security': args.enable_gitlab_security,
130
138
  'gitlab_security_file': args.gitlab_security_file,
131
139
  'disable_overview': args.disable_overview,
@@ -204,6 +212,11 @@ class CliConfig:
204
212
  logging.error("--workspace-name requires --sub-path to be specified")
205
213
  exit(1)
206
214
 
215
+ # Validate that sarif_reachable_only requires reach
216
+ if args.sarif_reachable_only and not args.reach:
217
+ logging.error("--sarif-reachable-only requires --reach to be specified")
218
+ exit(1)
219
+
207
220
  # Validate that only_facts_file requires reach
208
221
  if args.only_facts_file and not args.reach:
209
222
  logging.error("--only-facts-file requires --reach to be specified")
@@ -471,6 +484,19 @@ def create_argument_parser() -> argparse.ArgumentParser:
471
484
  action="store_true",
472
485
  help="Enable SARIF output of results instead of table or JSON format"
473
486
  )
487
+ output_group.add_argument(
488
+ "--sarif-file",
489
+ dest="sarif_file",
490
+ metavar="<path>",
491
+ default=None,
492
+ help="Output file path for SARIF report (implies --enable-sarif)"
493
+ )
494
+ output_group.add_argument(
495
+ "--sarif-reachable-only",
496
+ dest="sarif_reachable_only",
497
+ action="store_true",
498
+ help="Filter SARIF output to only include reachable findings (requires --reach)"
499
+ )
474
500
  output_group.add_argument(
475
501
  "--enable-gitlab-security",
476
502
  dest="enable_gitlab_security",
@@ -88,19 +88,16 @@ class Core:
88
88
  return org_id, organizations[org_id]['slug']
89
89
  return None, None
90
90
 
91
- def get_sbom_data(self, full_scan_id: str) -> List[SocketArtifact]:
92
- """Returns the list of SBOM artifacts for a full scan."""
91
+ def get_sbom_data(self, full_scan_id: str) -> Dict[str, SocketArtifact]:
92
+ """Returns SBOM artifacts for a full scan keyed by artifact ID."""
93
93
  response = self.sdk.fullscans.stream(self.config.org_slug, full_scan_id, use_types=True)
94
- artifacts: List[SocketArtifact] = []
95
94
  if not response.success:
96
95
  log.debug(f"Failed to get SBOM data for full-scan {full_scan_id}")
97
96
  log.debug(response.message)
98
97
  return {}
99
98
  if not hasattr(response, "artifacts") or not response.artifacts:
100
- return artifacts
101
- for artifact_id in response.artifacts:
102
- artifacts.append(response.artifacts[artifact_id])
103
- return artifacts
99
+ return {}
100
+ return response.artifacts
104
101
 
105
102
  def get_sbom_data_list(self, artifacts_dict: Dict[str, SocketArtifact]) -> list[SocketArtifact]:
106
103
  """Converts artifacts dictionary to a list."""
@@ -414,15 +411,15 @@ class Core:
414
411
  # Expand brace patterns for each manifest pattern
415
412
  expanded_patterns = Core.expand_brace_pattern(pattern_str)
416
413
  for exp_pat in expanded_patterns:
417
- # If pattern doesn't contain '/', prepend '**/' to match files in any subdirectory
418
- # This ensures patterns like '*requirements.txt' match '.test/requirements.txt'
419
- if '/' not in exp_pat:
420
- exp_pat = f"**/{exp_pat}"
421
-
422
414
  for file in norm_files:
423
- # Use PurePath.match for glob-like matching
415
+ # Match the pattern as-is first (handles root-level files
416
+ # like "package.json" matching pattern "package.json")
424
417
  if PurePath(file).match(exp_pat):
425
418
  return True
419
+ # Also try with **/ prefix to match files in subdirectories
420
+ # (e.g. "src/requirements.txt" matching "*requirements.txt")
421
+ if '/' not in exp_pat and PurePath(file).match(f"**/{exp_pat}"):
422
+ return True
426
423
  return False
427
424
 
428
425
  def check_file_count_limit(self, file_count: int) -> dict:
@@ -1,8 +1,15 @@
1
1
  import json
2
2
  from dataclasses import dataclass, field
3
- from typing import Dict, List, TypedDict, Any, Optional
3
+ from typing import Dict, List, Optional, TypedDict
4
4
 
5
- from socketdev.fullscans import FullScanMetadata, SocketArtifact, SocketArtifactLink, DiffType, SocketManifestReference, SocketScore, SocketAlert
5
+ from socketdev.fullscans import (
6
+ FullScanMetadata,
7
+ SocketAlert,
8
+ SocketArtifact,
9
+ SocketArtifactLink,
10
+ SocketManifestReference,
11
+ SocketScore,
12
+ )
6
13
 
7
14
  __all__ = [
8
15
  "Report",
@@ -109,8 +116,8 @@ class Package():
109
116
  type: str
110
117
  name: str
111
118
  version: str
112
- release: str
113
- diffType: str
119
+ release: Optional[str] = None
120
+ diffType: Optional[str] = None
114
121
  id: str
115
122
  author: List[str] = field(default_factory=list)
116
123
  score: SocketScore
@@ -158,6 +165,8 @@ class Package():
158
165
  name=data["name"],
159
166
  version=data["version"],
160
167
  type=data["type"],
168
+ release=data.get("release"),
169
+ diffType=data.get("diffType"),
161
170
  score=data["score"],
162
171
  alerts=data["alerts"],
163
172
  author=data.get("author", []),
@@ -187,10 +196,36 @@ class Package():
187
196
  Raises:
188
197
  ValueError: If reference data cannot be found in DiffArtifact
189
198
  """
199
+ diff_type = data.get("diffType")
200
+ if hasattr(diff_type, "value"):
201
+ diff_type = diff_type.value
202
+
203
+ # Newer API responses may provide flattened diff artifacts without refs.
204
+ if "topLevelAncestors" in data or (not data.get("head") and not data.get("base")):
205
+ return cls(
206
+ id=data["id"],
207
+ name=data["name"],
208
+ version=data["version"],
209
+ type=data["type"],
210
+ score=data.get("score", data.get("scores", {})),
211
+ alerts=data.get("alerts", []),
212
+ author=data.get("author", []),
213
+ size=data.get("size"),
214
+ license=data.get("license"),
215
+ topLevelAncestors=data.get("topLevelAncestors", []),
216
+ direct=data.get("direct", True),
217
+ manifestFiles=data.get("manifestFiles", []),
218
+ dependencies=data.get("dependencies"),
219
+ artifact=data.get("artifact"),
220
+ namespace=data.get("namespace"),
221
+ release=data.get("release"),
222
+ diffType=diff_type,
223
+ )
224
+
190
225
  ref = None
191
- if data["diffType"] in ["added", "updated", "unchanged"] and data.get("head"):
226
+ if diff_type in ["added", "updated", "unchanged"] and data.get("head"):
192
227
  ref = data["head"][0]
193
- elif data["diffType"] in ["removed", "replaced"] and data.get("base"):
228
+ elif diff_type in ["removed", "replaced"] and data.get("base"):
194
229
  ref = data["base"][0]
195
230
 
196
231
  if not ref:
@@ -201,8 +236,8 @@ class Package():
201
236
  name=data["name"],
202
237
  version=data["version"],
203
238
  type=data["type"],
204
- score=data["score"],
205
- alerts=data["alerts"],
239
+ score=data.get("score", data.get("scores", {})),
240
+ alerts=data.get("alerts", []),
206
241
  author=data.get("author", []),
207
242
  size=data.get("size"),
208
243
  license=data.get("license"),
@@ -213,7 +248,7 @@ class Package():
213
248
  artifact=ref.get("artifact"),
214
249
  namespace=data.get('namespace', None),
215
250
  release=ref.get("release", None),
216
- diffType=ref.get("diffType", None),
251
+ diffType=ref.get("diffType", diff_type),
217
252
  )
218
253
 
219
254
  class Issue:
@@ -58,12 +58,20 @@ class OutputHandler:
58
58
  slack_url = "Not configured"
59
59
  if self.config.slack_plugin.config and self.config.slack_plugin.config.get("url"):
60
60
  slack_url = self.config.slack_plugin.config.get("url")
61
+ slack_mode = (self.config.slack_plugin.config or {}).get("mode", "webhook")
62
+ bot_token = os.getenv("SOCKET_SLACK_BOT_TOKEN")
63
+ bot_token_status = "Set" if bot_token else "Not set"
61
64
  self.logger.debug("=== Slack Webhook Debug Information ===")
62
65
  self.logger.debug(f"Slack Plugin Enabled: {self.config.slack_plugin.enabled}")
66
+ self.logger.debug(f"Slack Mode: {slack_mode}")
63
67
  self.logger.debug(f"SOCKET_SLACK_ENABLED environment variable: {slack_enabled_env}")
64
68
  self.logger.debug(f"SOCKET_SLACK_CONFIG_JSON environment variable: {slack_config_env}")
65
69
  self.logger.debug(f"Slack Webhook URL: {slack_url}")
70
+ self.logger.debug(f"SOCKET_SLACK_BOT_TOKEN: {bot_token_status}")
66
71
  self.logger.debug(f"Slack Alert Levels: {self.config.slack_plugin.levels}")
72
+ if self.config.reach:
73
+ facts_path = os.path.join(self.config.target_path or ".", self.config.reach_output_file or ".socket.facts.json")
74
+ self.logger.debug(f"Reachability facts file: {facts_path} (exists: {os.path.exists(facts_path)})")
67
75
  self.logger.debug("=====================================")
68
76
 
69
77
  if self.config.slack_plugin.enabled:
@@ -139,14 +147,38 @@ class OutputHandler:
139
147
  def output_console_sarif(self, diff_report: Diff, sbom_file_name: Optional[str] = None) -> None:
140
148
  """
141
149
  Generate SARIF output from the diff report and print to console.
150
+ If --sarif-file is configured, also save to file.
151
+ If --sarif-reachable-only is set, filters to blocking (reachable) alerts only.
142
152
  """
143
153
  if diff_report.id != "NO_DIFF_RAN":
154
+ # When --sarif-reachable-only is set, filter to error=True alerts only.
155
+ # This mirrors the Slack plugin's reachability_alerts_only behaviou:
156
+ # when --reach is used, error=True reflects Socket's reachability-aware policy.
157
+ if self.config.sarif_reachable_only:
158
+ filtered_alerts = [a for a in diff_report.new_alerts if getattr(a, "error", False)]
159
+ diff_report = Diff(
160
+ new_alerts=filtered_alerts,
161
+ diff_url=getattr(diff_report, "diff_url", ""),
162
+ new_packages=getattr(diff_report, "new_packages", []),
163
+ removed_packages=getattr(diff_report, "removed_packages", []),
164
+ packages=getattr(diff_report, "packages", {}),
165
+ )
166
+ diff_report.id = "filtered"
167
+
144
168
  # Generate the SARIF structure using Messages
145
169
  console_security_comment = Messages.create_security_comment_sarif(diff_report)
146
170
  self.save_sbom_file(diff_report, sbom_file_name)
147
171
  # Print the SARIF output to the console in JSON format
148
172
  print(json.dumps(console_security_comment, indent=2))
149
173
 
174
+ # Save to file if --sarif-file is specified
175
+ if self.config.sarif_file:
176
+ sarif_path = Path(self.config.sarif_file)
177
+ sarif_path.parent.mkdir(parents=True, exist_ok=True)
178
+ with open(sarif_path, "w") as f:
179
+ json.dump(console_security_comment, f, indent=2)
180
+ self.logger.info(f"SARIF report saved to {self.config.sarif_file}")
181
+
150
182
  def report_pass(self, diff_report: Diff) -> bool:
151
183
  """Determines if the report passes security checks"""
152
184
  # Priority 1: --disable-blocking always passes
@@ -135,18 +135,20 @@ class SlackPlugin(Plugin):
135
135
  if not bot_token:
136
136
  logger.error("SOCKET_SLACK_BOT_TOKEN environment variable not set for bot mode.")
137
137
  return
138
-
138
+
139
139
  if not bot_token.startswith("xoxb-"):
140
140
  logger.error("SOCKET_SLACK_BOT_TOKEN must start with 'xoxb-' (Bot User OAuth Token).")
141
141
  return
142
-
142
+
143
+ logger.debug("SOCKET_SLACK_BOT_TOKEN: Set (valid xoxb- format)")
144
+
143
145
  # Get bot_configs from configuration
144
146
  bot_configs = self.config.get("bot_configs", [])
145
-
147
+
146
148
  if not bot_configs:
147
149
  logger.warning("No bot_configs configured for bot mode.")
148
150
  return
149
-
151
+
150
152
  logger.debug("Slack Plugin Enabled (bot mode)")
151
153
  logger.debug("Alert levels: %s", self.config.get("levels"))
152
154
  logger.debug(f"Number of bot_configs: {len(bot_configs)}")
@@ -212,29 +214,35 @@ class SlackPlugin(Plugin):
212
214
  """Send reachability alerts using bot mode with Slack API."""
213
215
  # Construct path to socket facts file
214
216
  facts_file_path = os.path.join(config.target_path or ".", f"{config.reach_output_file}")
215
- logger.debug(f"Loading reachability data from {facts_file_path}")
216
-
217
+ facts_file_exists = os.path.exists(facts_file_path)
218
+ logger.debug(f"Loading reachability data from {facts_file_path} (exists: {facts_file_exists})")
219
+
220
+ if not facts_file_exists:
221
+ logger.error(f"Reachability facts file not found: {facts_file_path} — was --reach run successfully?")
222
+ return
223
+
217
224
  # Load socket facts file
218
225
  facts_data = load_socket_facts(facts_file_path)
219
-
226
+
220
227
  if not facts_data:
221
- logger.debug("No .socket.facts.json file found or failed to load")
228
+ logger.error(f"Failed to load or parse reachability facts file: {facts_file_path}")
222
229
  return
223
-
230
+
224
231
  # Get components with vulnerabilities
225
232
  components_with_vulns = get_components_with_vulnerabilities(facts_data)
226
-
233
+ logger.debug(f"Components with vulnerabilities in facts file: {len(components_with_vulns) if components_with_vulns else 0}")
234
+
227
235
  if not components_with_vulns:
228
236
  logger.debug("No components with vulnerabilities found in .socket.facts.json")
229
237
  return
230
-
238
+
231
239
  # Convert to alerts format
232
240
  components_with_alerts = convert_to_alerts(components_with_vulns)
233
-
241
+
234
242
  if not components_with_alerts:
235
243
  logger.debug("No alerts generated from .socket.facts.json")
236
244
  return
237
-
245
+
238
246
  logger.debug(f"Found {len(components_with_alerts)} components with reachability alerts")
239
247
 
240
248
  # Send to each configured bot_config with filtering
@@ -265,10 +273,12 @@ class SlackPlugin(Plugin):
265
273
  filtered_component['alerts'] = filtered_component_alerts
266
274
  filtered_components.append(filtered_component)
267
275
 
276
+ logger.debug(f"Bot config '{name}': {len(filtered_components)} components after severity filter {bot_config.get('severities', '(all)')}")
277
+
268
278
  if not filtered_components:
269
279
  logger.debug(f"No reachability alerts match filter criteria for bot_config '{name}'. Skipping.")
270
280
  continue
271
-
281
+
272
282
  # Format for Slack using the formatter (max 45 blocks for findings + 5 for header/footer)
273
283
  slack_notifications = format_socket_facts_for_slack(
274
284
  filtered_components,