socketsecurity 2.2.93__tar.gz → 2.3.0__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 (136) hide show
  1. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/CHANGELOG.md +46 -0
  2. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/PKG-INFO +43 -1
  3. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/README.md +42 -0
  4. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/pyproject.toml +1 -1
  5. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/__init__.py +1 -1
  6. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/config.py +30 -0
  7. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/__init__.py +10 -5
  8. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/socketcli.py +59 -7
  9. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/core/test_sdk_methods.py +1 -0
  10. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_cli_config.py +39 -0
  11. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_socketcli.py +96 -0
  12. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/uv.lock +1 -1
  13. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.github/CODEOWNERS +0 -0
  14. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
  15. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
  16. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
  17. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  18. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.github/dependabot.yml +0 -0
  19. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.github/workflows/dependabot-review.yml +0 -0
  20. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.github/workflows/docker-stable.yml +0 -0
  21. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.github/workflows/e2e-test.yml +0 -0
  22. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.github/workflows/pr-preview.yml +0 -0
  23. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.github/workflows/python-tests.yml +0 -0
  24. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.github/workflows/release.yml +0 -0
  25. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.github/workflows/version-check.yml +0 -0
  26. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.github/zizmor.yml +0 -0
  27. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.gitignore +0 -0
  28. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.hooks/sync_version.py +0 -0
  29. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.pre-commit-config.yaml +0 -0
  30. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/.python-version +0 -0
  31. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/Dockerfile +0 -0
  32. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/LICENSE +0 -0
  33. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/Makefile +0 -0
  34. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/docs/ci-cd.md +0 -0
  35. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/docs/cli-reference.md +0 -0
  36. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/docs/development.md +0 -0
  37. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/docs/troubleshooting.md +0 -0
  38. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/examples/config/sarif-dashboard-parity.json +0 -0
  39. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/examples/config/sarif-dashboard-parity.toml +0 -0
  40. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/examples/config/sarif-diff-ci-cd.json +0 -0
  41. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/examples/config/sarif-diff-ci-cd.toml +0 -0
  42. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/examples/config/sarif-instance-detail.json +0 -0
  43. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/examples/config/sarif-instance-detail.toml +0 -0
  44. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/instructions/gitlab-commit-status/uat.md +0 -0
  45. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/pytest.ini +0 -0
  46. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/scripts/build_container.sh +0 -0
  47. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/scripts/build_container_flexible.sh +0 -0
  48. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/scripts/deploy-test-docker.sh +0 -0
  49. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/scripts/deploy-test-pypi.sh +0 -0
  50. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/scripts/docker-entrypoint.sh +0 -0
  51. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/scripts/run.sh +0 -0
  52. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/session.md +0 -0
  53. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socket.yml +0 -0
  54. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/alert_selection.py +0 -0
  55. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/classes.py +0 -0
  56. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/cli_client.py +0 -0
  57. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/exceptions.py +0 -0
  58. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/git_interface.py +0 -0
  59. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/helper/__init__.py +0 -0
  60. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/helper/socket_facts_loader.py +0 -0
  61. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/lazy_file_loader.py +0 -0
  62. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/logging.py +0 -0
  63. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/messages.py +0 -0
  64. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/resource_utils.py +0 -0
  65. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/scm/__init__.py +0 -0
  66. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/scm/base.py +0 -0
  67. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/scm/client.py +0 -0
  68. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/scm/github.py +0 -0
  69. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/scm/gitlab.py +0 -0
  70. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/scm_comments.py +0 -0
  71. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/socket_config.py +0 -0
  72. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/tools/reachability.py +0 -0
  73. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/core/utils.py +0 -0
  74. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/fossa_compat.py +0 -0
  75. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/output.py +0 -0
  76. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/plugins/__init__.py +0 -0
  77. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/plugins/base.py +0 -0
  78. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/plugins/formatters/__init__.py +0 -0
  79. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/plugins/formatters/slack.py +0 -0
  80. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/plugins/jira.py +0 -0
  81. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/plugins/manager.py +0 -0
  82. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/plugins/slack.py +0 -0
  83. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/plugins/teams.py +0 -0
  84. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/socketsecurity/plugins/webhook.py +0 -0
  85. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/__init__.py +0 -0
  86. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/core/conftest.py +0 -0
  87. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/core/create_diff_input.json +0 -0
  88. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/core/test_diff_alerts.py +0 -0
  89. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/core/test_diff_generation.py +0 -0
  90. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/core/test_has_manifest_files.py +0 -0
  91. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/core/test_package_and_alerts.py +0 -0
  92. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/core/test_supporting_methods.py +0 -0
  93. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/data/fullscans/create_response.json +0 -0
  94. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/data/fullscans/diff/stream_diff.json +0 -0
  95. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
  96. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/data/fullscans/head_scan/metadata.json +0 -0
  97. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
  98. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
  99. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/data/fullscans/new_scan/metadata.json +0 -0
  100. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
  101. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/data/repos/repo_info_error.json +0 -0
  102. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/data/repos/repo_info_no_head.json +0 -0
  103. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/data/repos/repo_info_success.json +0 -0
  104. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/data/settings/security-policy.json +0 -0
  105. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/e2e/fixtures/simple-npm/index.js +0 -0
  106. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/e2e/fixtures/simple-npm/package.json +0 -0
  107. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/e2e/fixtures/simple-pypi/requirements.txt +0 -0
  108. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/e2e/validate-gitlab.sh +0 -0
  109. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/e2e/validate-json.sh +0 -0
  110. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/e2e/validate-reachability.sh +0 -0
  111. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/e2e/validate-sarif.sh +0 -0
  112. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/e2e/validate-scan.sh +0 -0
  113. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/fixtures/fossa/README.md +0 -0
  114. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/fixtures/fossa/fossa-analyze-empty.json +0 -0
  115. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/fixtures/fossa/fossa-analyze-populated.json +0 -0
  116. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/fixtures/fossa/fossa-sbom-empty-deep.json +0 -0
  117. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/fixtures/fossa/fossa-sbom-populated.json +0 -0
  118. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/__init__.py +0 -0
  119. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_alert_selection.py +0 -0
  120. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_client.py +0 -0
  121. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_config.py +0 -0
  122. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_dependency_overview.py +0 -0
  123. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_disable_ignore.py +0 -0
  124. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_fossa_compat.py +0 -0
  125. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_fossa_parity.py +0 -0
  126. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_gitlab_auth.py +0 -0
  127. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_gitlab_auth_fallback.py +0 -0
  128. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_gitlab_commit_status.py +0 -0
  129. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_gitlab_format.py +0 -0
  130. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_ignore_telemetry_filtering.py +0 -0
  131. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_output.py +0 -0
  132. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/tests/unit/test_slack_plugin.py +0 -0
  133. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/workflows/bitbucket-pipelines.yml +0 -0
  134. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/workflows/buildkite.yml +0 -0
  135. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/workflows/github-actions.yml +0 -0
  136. {socketsecurity-2.2.93 → socketsecurity-2.3.0}/workflows/gitlab-ci.yml +0 -0
@@ -1,5 +1,51 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.3.0
4
+
5
+ ### New: `--exit-code-on-api-error`
6
+
7
+ Adds a configurable exit code for API / infrastructure failures (timeouts,
8
+ network errors, unexpected exceptions), so CI pipelines can distinguish them
9
+ from blocking security findings (exit `1`):
10
+
11
+ ```
12
+ socketcli --exit-code-on-api-error 100 ...
13
+ ```
14
+
15
+ Default is `3` (the code the CLI already used for these errors), so **default
16
+ behavior is unchanged** — the exit code only changes when you pass the flag.
17
+ Set it to a Buildkite `soft_fail` code, or to `0` to swallow infra errors.
18
+
19
+ **Interaction to be aware of:** `--disable-blocking` forces exit `0` for *all*
20
+ outcomes and therefore overrides `--exit-code-on-api-error`. Use the new flag
21
+ *without* `--disable-blocking` if you want a custom infra-error code to take
22
+ effect. See the exit-code reference in the README.
23
+
24
+ > A future `3.0` release is planned to make infrastructure errors exit non-zero
25
+ > even under `--disable-blocking` (so outages stop being silently swallowed).
26
+ > That is a breaking change and is intentionally **not** in this release.
27
+
28
+ ### New: commit message auto-truncation
29
+
30
+ `--commit-message` values longer than 200 characters are now automatically
31
+ truncated before being sent to the API, preventing HTTP 413 errors from
32
+ oversized URL query parameters (common with AI-generated commit messages or
33
+ `$BUILDKITE_MESSAGE`).
34
+
35
+ ### Improved: Buildkite log formatting
36
+
37
+ When running inside a Buildkite job (`BUILDKITE=true`), infrastructure errors
38
+ emit Buildkite log section markers (`^^^ +++` / `--- :warning:`) so the error
39
+ section auto-expands in the BK UI, plus a `soft_fail` hint. No effect on other
40
+ CI platforms.
41
+
42
+ ### Fixed
43
+
44
+ - `--timeout` is now honored end-to-end: it was only applied to the local
45
+ `CliClient`, but the full-scan diff comparison uses the Socket SDK instance,
46
+ which was constructed without the CLI timeout and defaulted to 1200s.
47
+ - `--exclude-license-details` now propagates to the full-scan diff comparison
48
+ request (it was only applied to full-scan params / report URLs before).
3
49
  ## 2.2.93
4
50
 
5
51
  - Bundled twelve Dependabot dependency updates: `urllib3`, `gitpython`, `python-dotenv`, `pytest`, `uv`, `cryptography`, `pygments`, `requests`, and `idna` (main app), plus `axios`, `requests`, and `flask` (e2e fixtures). `idna` 3.11 → 3.15 includes the fix for CVE-2026-45409.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: socketsecurity
3
- Version: 2.2.93
3
+ Version: 2.3.0
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>
@@ -252,6 +252,48 @@ Minimal pattern:
252
252
  SOCKET_SECURITY_API_TOKEN: ${{ secrets.SOCKET_SECURITY_API_TOKEN }}
253
253
  ```
254
254
 
255
+ ## Exit codes
256
+
257
+ | Code | Meaning |
258
+ |------|---------|
259
+ | `0` | Clean scan — no blocking issues (or `--disable-blocking` set) |
260
+ | `1` | Blocking security finding(s) detected |
261
+ | `2` | Scan interrupted (SIGINT / Ctrl+C) |
262
+ | `3` | Infrastructure or API error (timeout, network failure, unexpected error) |
263
+
264
+ `--exit-code-on-api-error <N>` remaps the infrastructure-error code (`3`) to any
265
+ value — e.g. a Buildkite `soft_fail` code, or `0` to swallow infra errors. Exit
266
+ `3` is a Socket convention, not an industry standard.
267
+
268
+ ### How these options interact
269
+
270
+ The two flags that affect exit codes can cancel each other out, so the order of
271
+ precedence matters:
272
+
273
+ - **`--disable-blocking` wins over everything.** It forces exit `0` for *all*
274
+ outcomes — security findings *and* infrastructure errors. If you set it,
275
+ `--exit-code-on-api-error` has no effect (you'll always get `0`).
276
+ - **`--exit-code-on-api-error` only applies when `--disable-blocking` is *not*
277
+ set.** It changes the infra-error code (and the generic-error code); it never
278
+ touches the security-finding code (`1`).
279
+
280
+ So for the common "don't let Socket outages block my pipeline, but still fail on
281
+ real findings" goal, use `--exit-code-on-api-error` **without** `--disable-blocking`:
282
+
283
+ ```yaml
284
+ # Buildkite: soft-fail only on infrastructure errors, still block on findings
285
+ steps:
286
+ - label: ":lock: Socket Security Scan"
287
+ command: "socketcli --exit-code-on-api-error 100 ..." # NOT --disable-blocking
288
+ soft_fail:
289
+ - exit_status: 100
290
+ ```
291
+
292
+ Combining `--disable-blocking` with `--exit-code-on-api-error 100` would make the
293
+ scan exit `0` on *both* findings and outages — the `soft_fail: 100` rule would
294
+ never match, and real findings would stop blocking. That's usually not what you
295
+ want.
296
+
255
297
  ## Common gotchas
256
298
 
257
299
  See [`docs/troubleshooting.md`](https://github.com/SocketDev/socket-python-cli/blob/main/docs/troubleshooting.md#common-gotchas).
@@ -194,6 +194,48 @@ Minimal pattern:
194
194
  SOCKET_SECURITY_API_TOKEN: ${{ secrets.SOCKET_SECURITY_API_TOKEN }}
195
195
  ```
196
196
 
197
+ ## Exit codes
198
+
199
+ | Code | Meaning |
200
+ |------|---------|
201
+ | `0` | Clean scan — no blocking issues (or `--disable-blocking` set) |
202
+ | `1` | Blocking security finding(s) detected |
203
+ | `2` | Scan interrupted (SIGINT / Ctrl+C) |
204
+ | `3` | Infrastructure or API error (timeout, network failure, unexpected error) |
205
+
206
+ `--exit-code-on-api-error <N>` remaps the infrastructure-error code (`3`) to any
207
+ value — e.g. a Buildkite `soft_fail` code, or `0` to swallow infra errors. Exit
208
+ `3` is a Socket convention, not an industry standard.
209
+
210
+ ### How these options interact
211
+
212
+ The two flags that affect exit codes can cancel each other out, so the order of
213
+ precedence matters:
214
+
215
+ - **`--disable-blocking` wins over everything.** It forces exit `0` for *all*
216
+ outcomes — security findings *and* infrastructure errors. If you set it,
217
+ `--exit-code-on-api-error` has no effect (you'll always get `0`).
218
+ - **`--exit-code-on-api-error` only applies when `--disable-blocking` is *not*
219
+ set.** It changes the infra-error code (and the generic-error code); it never
220
+ touches the security-finding code (`1`).
221
+
222
+ So for the common "don't let Socket outages block my pipeline, but still fail on
223
+ real findings" goal, use `--exit-code-on-api-error` **without** `--disable-blocking`:
224
+
225
+ ```yaml
226
+ # Buildkite: soft-fail only on infrastructure errors, still block on findings
227
+ steps:
228
+ - label: ":lock: Socket Security Scan"
229
+ command: "socketcli --exit-code-on-api-error 100 ..." # NOT --disable-blocking
230
+ soft_fail:
231
+ - exit_status: 100
232
+ ```
233
+
234
+ Combining `--disable-blocking` with `--exit-code-on-api-error 100` would make the
235
+ scan exit `0` on *both* findings and outages — the `soft_fail: 100` rule would
236
+ never match, and real findings would stop blocking. That's usually not what you
237
+ want.
238
+
197
239
  ## Common gotchas
198
240
 
199
241
  See [`docs/troubleshooting.md`](https://github.com/SocketDev/socket-python-cli/blob/main/docs/troubleshooting.md#common-gotchas).
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "socketsecurity"
9
- version = "2.2.93"
9
+ version = "2.3.0"
10
10
  requires-python = ">= 3.11"
11
11
  license = {"file" = "LICENSE"}
12
12
  dependencies = [
@@ -1,3 +1,3 @@
1
1
  __author__ = 'socket.dev'
2
- __version__ = '2.2.93'
2
+ __version__ = '2.3.0'
3
3
  USER_AGENT = f'SocketPythonCLI/{__version__}'
@@ -101,6 +101,7 @@ class CliConfig:
101
101
  pending_head: bool = False
102
102
  enable_diff: bool = False
103
103
  timeout: Optional[int] = 1200
104
+ exit_code_on_api_error: int = 3
104
105
  exclude_license_details: bool = False
105
106
  include_module_folders: bool = False
106
107
  repo_is_public: bool = False
@@ -182,6 +183,19 @@ class CliConfig:
182
183
  if commit_message and commit_message.startswith('"') and commit_message.endswith('"'):
183
184
  commit_message = commit_message[1:-1]
184
185
 
186
+ # Truncate to avoid 413s from oversized URL query parameters.
187
+ # The API has no application-layer length validation on commit_message;
188
+ # the 413 originates from an infrastructure-layer URL length limit
189
+ # (nginx/Cloudflare). 200 chars chosen as a conservative ceiling given
190
+ # URL encoding can 2-3x raw character count.
191
+ MAX_COMMIT_MESSAGE_LENGTH = 200
192
+ if commit_message and len(commit_message) > MAX_COMMIT_MESSAGE_LENGTH:
193
+ logging.debug(
194
+ f"commit_message truncated from {len(commit_message)} to "
195
+ f"{MAX_COMMIT_MESSAGE_LENGTH} characters to avoid API request size limits"
196
+ )
197
+ commit_message = commit_message[:MAX_COMMIT_MESSAGE_LENGTH]
198
+
185
199
  config_args = {
186
200
  'api_token': api_token,
187
201
  'repo': args.repo,
@@ -219,6 +233,7 @@ class CliConfig:
219
233
  'integration_type': args.integration,
220
234
  'pending_head': args.pending_head,
221
235
  'timeout': args.timeout,
236
+ 'exit_code_on_api_error': args.exit_code_on_api_error,
222
237
  'exclude_license_details': args.exclude_license_details,
223
238
  'include_module_folders': args.include_module_folders,
224
239
  'repo_is_public': args.repo_is_public,
@@ -802,6 +817,21 @@ def create_argument_parser() -> argparse.ArgumentParser:
802
817
  help="Timeout in seconds for API requests",
803
818
  required=False
804
819
  )
820
+ advanced_group.add_argument(
821
+ "--exit-code-on-api-error",
822
+ dest="exit_code_on_api_error",
823
+ type=int,
824
+ default=3,
825
+ metavar="<int>",
826
+ help=(
827
+ "Exit code to use when the CLI fails on an API or infrastructure error "
828
+ "(timeout, network failure, unexpected exception). Default: 3. Useful for "
829
+ "distinguishing infrastructure failures from security findings (exit 1) in "
830
+ "CI -- e.g. set to a Buildkite soft_fail code. NOTE: --disable-blocking "
831
+ "forces exit 0 for ALL outcomes and therefore overrides this flag; do not "
832
+ "combine the two if you want the custom code to take effect."
833
+ )
834
+ )
805
835
  advanced_group.add_argument(
806
836
  "--allow-unverified",
807
837
  action="store_true",
@@ -941,7 +941,8 @@ class Core:
941
941
  def get_added_and_removed_packages(
942
942
  self,
943
943
  head_full_scan_id: str,
944
- new_full_scan_id: str
944
+ new_full_scan_id: str,
945
+ include_license_details: bool = True
945
946
  ) -> Tuple[Dict[str, Package], Dict[str, Package], Dict[str, Package]]:
946
947
  """
947
948
  Get packages that were added and removed between scans.
@@ -958,12 +959,12 @@ class Core:
958
959
  diff_start = time.time()
959
960
  try:
960
961
  diff_report = (
961
- self.sdk.fullscans.stream_diff
962
- (
962
+ self.sdk.fullscans.stream_diff(
963
963
  self.config.org_slug,
964
964
  head_full_scan_id,
965
965
  new_full_scan_id,
966
- use_types=True
966
+ use_types=True,
967
+ include_license_details=str(include_license_details).lower()
967
968
  ).data
968
969
  )
969
970
  except APIFailure as e:
@@ -1175,7 +1176,11 @@ class Core:
1175
1176
  added_packages,
1176
1177
  removed_packages,
1177
1178
  packages
1178
- ) = self.get_added_and_removed_packages(head_full_scan_id, new_full_scan.id)
1179
+ ) = self.get_added_and_removed_packages(
1180
+ head_full_scan_id,
1181
+ new_full_scan.id,
1182
+ include_license_details=getattr(params, "include_license_details", True)
1183
+ )
1179
1184
 
1180
1185
  # Separate unchanged packages from added/removed for --strict-blocking support
1181
1186
  unchanged_packages = {
@@ -27,6 +27,37 @@ socket_logger, log = initialize_logging()
27
27
 
28
28
  load_dotenv()
29
29
 
30
+ # Buildkite sets BUILDKITE=true in every job environment. Used to gate log
31
+ # section markers that would render as literal text on other CI platforms.
32
+ IS_BUILDKITE = os.getenv("BUILDKITE") == "true"
33
+
34
+
35
+ def _emit_infrastructure_error(message: str, include_traceback: bool = False) -> None:
36
+ """Emit a structured error for infrastructure/API failures.
37
+
38
+ When running in Buildkite, wraps the error in log-section markers
39
+ (`^^^ +++` expands the section in the BK UI) and prints a soft_fail hint.
40
+ On every other platform it's a plain log.error so the markers don't leak
41
+ as literal text. This is presentation only -- it does not decide the exit
42
+ code (the caller does that, honoring --disable-blocking and
43
+ --exit-code-on-api-error).
44
+ """
45
+ if IS_BUILDKITE:
46
+ print("^^^ +++", flush=True)
47
+ print("--- :warning: Socket infrastructure error", flush=True)
48
+
49
+ log.error(message)
50
+
51
+ if IS_BUILDKITE:
52
+ log.error(
53
+ "Tip: this is an infrastructure error, not a security finding. To keep it "
54
+ "from blocking the build, add a soft_fail rule for the CLI's API-error exit "
55
+ "code (default 3, or whatever you pass to --exit-code-on-api-error)."
56
+ )
57
+
58
+ if include_traceback:
59
+ traceback.print_exc()
60
+
30
61
 
31
62
  def build_license_artifact_payload(
32
63
  diff: Diff,
@@ -62,6 +93,23 @@ def _write_attribution_file(config, payload: dict) -> None:
62
93
  Core.save_file(config.license_file_name, json.dumps(payload, indent=2))
63
94
 
64
95
 
96
+ DEFAULT_API_TIMEOUT = 1200
97
+
98
+
99
+ def get_api_request_timeout(config: CliConfig) -> int:
100
+ return config.timeout if config.timeout is not None else DEFAULT_API_TIMEOUT
101
+
102
+
103
+ def build_socket_sdk(config: CliConfig) -> socketdev:
104
+ cli_user_agent_string = f"SocketPythonCLI/{config.version}"
105
+ return socketdev(
106
+ token=config.api_token,
107
+ timeout=get_api_request_timeout(config),
108
+ allow_unverified=config.allow_unverified,
109
+ user_agent=cli_user_agent_string
110
+ )
111
+
112
+
65
113
  def cli():
66
114
  try:
67
115
  main_code()
@@ -73,12 +121,17 @@ def cli():
73
121
  else:
74
122
  sys.exit(0)
75
123
  except Exception as error:
76
- log.error("Unexpected error when running the cli")
77
- log.error(error)
78
- traceback.print_exc()
79
124
  config = CliConfig.from_args() # Get current config
125
+ _emit_infrastructure_error(
126
+ f"Unexpected error when running the CLI: {error}",
127
+ include_traceback=True,
128
+ )
129
+ # --disable-blocking forces a clean exit for ALL outcomes (it takes
130
+ # precedence over --exit-code-on-api-error); otherwise infra/API errors
131
+ # exit with the configurable code (default 3), keeping them distinct
132
+ # from blocking security findings (exit 1).
80
133
  if not config.disable_blocking:
81
- sys.exit(3)
134
+ sys.exit(config.exit_code_on_api_error)
82
135
  else:
83
136
  sys.exit(0)
84
137
 
@@ -99,8 +152,7 @@ def main_code():
99
152
  "1. Command line: --api-token YOUR_TOKEN\n"
100
153
  "2. Environment variable: SOCKET_SECURITY_API_TOKEN")
101
154
  sys.exit(3)
102
- cli_user_agent_string = f"SocketPythonCLI/{config.version}"
103
- sdk = socketdev(token=config.api_token, allow_unverified=config.allow_unverified, user_agent=cli_user_agent_string)
155
+ sdk = build_socket_sdk(config)
104
156
 
105
157
  # Suppress urllib3 InsecureRequestWarning when using --allow-unverified
106
158
  if config.allow_unverified:
@@ -119,7 +171,7 @@ def main_code():
119
171
  socket_config = SocketConfig(
120
172
  api_key=config.api_token,
121
173
  allow_unverified_ssl=config.allow_unverified,
122
- timeout=config.timeout if config.timeout is not None else 1200 # Use CLI timeout if provided
174
+ timeout=get_api_request_timeout(config)
123
175
  )
124
176
  log.debug("loaded socket_config")
125
177
  client = CliClient(socket_config)
@@ -101,6 +101,7 @@ def test_get_added_and_removed_packages(core):
101
101
  "head",
102
102
  "new",
103
103
  use_types=True,
104
+ include_license_details="true",
104
105
  )
105
106
 
106
107
  # Verify the results
@@ -1,6 +1,45 @@
1
1
  import pytest
2
2
  from socketsecurity.config import CliConfig
3
3
 
4
+
5
+ class TestExitCodeOnApiError:
6
+ def test_default_is_3(self):
7
+ config = CliConfig.from_args(["--api-token", "test"])
8
+ assert config.exit_code_on_api_error == 3
9
+
10
+ def test_custom_value(self):
11
+ config = CliConfig.from_args(
12
+ ["--api-token", "test", "--exit-code-on-api-error", "100"]
13
+ )
14
+ assert config.exit_code_on_api_error == 100
15
+
16
+ def test_zero_value(self):
17
+ config = CliConfig.from_args(
18
+ ["--api-token", "test", "--exit-code-on-api-error", "0"]
19
+ )
20
+ assert config.exit_code_on_api_error == 0
21
+
22
+
23
+ class TestCommitMessageTruncation:
24
+ def test_passes_through_under_limit(self):
25
+ msg = "a normal short commit message"
26
+ config = CliConfig.from_args(["--api-token", "test", "--commit-message", msg])
27
+ assert config.commit_message == msg
28
+
29
+ def test_truncated_above_limit(self):
30
+ config = CliConfig.from_args(
31
+ ["--api-token", "test", "--commit-message", "a" * 250]
32
+ )
33
+ assert config.commit_message == "a" * 200
34
+
35
+ def test_quote_strip_runs_before_truncation(self):
36
+ quoted = '"' + ("b" * 250) + '"'
37
+ config = CliConfig.from_args(
38
+ ["--api-token", "test", "--commit-message", quoted]
39
+ )
40
+ assert config.commit_message == "b" * 200
41
+
42
+
4
43
  class TestCliConfig:
5
44
  def test_api_token_from_env(self, monkeypatch):
6
45
  monkeypatch.setenv("SOCKET_SECURITY_API_KEY", "test-token")
@@ -1,7 +1,103 @@
1
+ import sys
2
+
3
+ import pytest
4
+
1
5
  from socketsecurity.core.classes import Diff, Package
6
+ from socketsecurity import socketcli
2
7
  from socketsecurity.socketcli import build_license_artifact_payload
3
8
 
4
9
 
10
+ # ---------------------------------------------------------------------------
11
+ # Exit-code-on-api-error (flag-only, non-breaking for 2.3.x).
12
+ #
13
+ # Default behavior is unchanged from prior releases: unexpected errors exit 3,
14
+ # and --disable-blocking forces exit 0 for everything. The flag only changes
15
+ # the code when explicitly set, and --disable-blocking still takes precedence.
16
+ # ---------------------------------------------------------------------------
17
+
18
+
19
+ def _run_cli_expecting_exit(monkeypatch, argv, boom=None):
20
+ def fail_main_code():
21
+ raise (boom or RuntimeError("infra boom"))
22
+
23
+ monkeypatch.setattr(socketcli, "main_code", fail_main_code)
24
+ monkeypatch.setattr(sys, "argv", argv)
25
+ with pytest.raises(SystemExit) as exc_info:
26
+ socketcli.cli()
27
+ return exc_info.value.code
28
+
29
+
30
+ def test_unexpected_error_exits_3_by_default(monkeypatch):
31
+ code = _run_cli_expecting_exit(monkeypatch, ["socketcli", "--api-token", "test"])
32
+ assert code == 3
33
+
34
+
35
+ def test_exit_code_on_api_error_remaps_failure(monkeypatch):
36
+ code = _run_cli_expecting_exit(
37
+ monkeypatch,
38
+ ["socketcli", "--api-token", "test", "--exit-code-on-api-error", "100"],
39
+ )
40
+ assert code == 100
41
+
42
+
43
+ def test_disable_blocking_overrides_exit_code_on_api_error(monkeypatch):
44
+ # The documented interaction: --disable-blocking forces exit 0 for ALL
45
+ # outcomes and therefore overrides --exit-code-on-api-error. A user who
46
+ # sets both gets 0, NOT 100 -- this guards against silently regressing
47
+ # that precedence (which would break the documented soft_fail guidance).
48
+ code = _run_cli_expecting_exit(
49
+ monkeypatch,
50
+ [
51
+ "socketcli", "--api-token", "test",
52
+ "--exit-code-on-api-error", "100",
53
+ "--disable-blocking",
54
+ ],
55
+ )
56
+ assert code == 0
57
+
58
+
59
+ def test_keyboard_interrupt_still_exits_2(monkeypatch):
60
+ code = _run_cli_expecting_exit(
61
+ monkeypatch, ["socketcli", "--api-token", "test"], boom=KeyboardInterrupt()
62
+ )
63
+ assert code == 2
64
+
65
+
66
+ # ---------------------------------------------------------------------------
67
+ # Buildkite-aware infrastructure error formatting.
68
+ # ---------------------------------------------------------------------------
69
+
70
+
71
+ def test_emit_infra_error_no_buildkite_has_no_markers(monkeypatch, capsys, caplog):
72
+ monkeypatch.setattr(socketcli, "IS_BUILDKITE", False)
73
+ with caplog.at_level("ERROR", logger="socketcli"):
74
+ socketcli._emit_infrastructure_error("something failed")
75
+ out = capsys.readouterr().out
76
+ assert "^^^ +++" not in out
77
+ assert "--- :warning:" not in out
78
+ assert "soft_fail" not in "\n".join(r.getMessage() for r in caplog.records)
79
+
80
+
81
+ def test_emit_infra_error_buildkite_emits_markers(monkeypatch, capsys, caplog):
82
+ monkeypatch.setattr(socketcli, "IS_BUILDKITE", True)
83
+ with caplog.at_level("ERROR", logger="socketcli"):
84
+ socketcli._emit_infrastructure_error("something failed")
85
+ out = capsys.readouterr().out
86
+ assert "^^^ +++" in out
87
+ assert "--- :warning: Socket infrastructure error" in out
88
+ assert "soft_fail" in "\n".join(r.getMessage() for r in caplog.records)
89
+
90
+
91
+ def test_emit_infra_error_traceback_gated(monkeypatch, capsys):
92
+ monkeypatch.setattr(socketcli, "IS_BUILDKITE", False)
93
+ try:
94
+ raise ValueError("boom")
95
+ except ValueError:
96
+ socketcli._emit_infrastructure_error("wrapped", include_traceback=True)
97
+ err = capsys.readouterr().err
98
+ assert "Traceback" in err and "ValueError: boom" in err
99
+
100
+
5
101
  def test_build_license_artifact_payload_without_packages_returns_empty_dict():
6
102
  diff = Diff()
7
103
 
@@ -1168,7 +1168,7 @@ wheels = [
1168
1168
 
1169
1169
  [[package]]
1170
1170
  name = "socketsecurity"
1171
- version = "2.2.93"
1171
+ version = "2.3.0"
1172
1172
  source = { editable = "." }
1173
1173
  dependencies = [
1174
1174
  { name = "bs4" },
File without changes
File without changes