socketsecurity 2.2.93__tar.gz → 2.3.1__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.
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/CHANGELOG.md +73 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/PKG-INFO +45 -1
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/README.md +42 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/pyproject.toml +3 -1
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/__init__.py +1 -1
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/config.py +30 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/__init__.py +139 -6
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/socketcli.py +59 -7
- socketsecurity-2.3.1/tests/core/test_facts_compression.py +137 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/core/test_sdk_methods.py +1 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_cli_config.py +39 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_socketcli.py +96 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/uv.lock +108 -2
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.github/CODEOWNERS +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.github/dependabot.yml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.github/workflows/dependabot-review.yml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.github/workflows/docker-stable.yml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.github/workflows/e2e-test.yml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.github/workflows/pr-preview.yml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.github/workflows/python-tests.yml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.github/workflows/release.yml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.github/workflows/version-check.yml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.github/zizmor.yml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.gitignore +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.hooks/sync_version.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.pre-commit-config.yaml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/.python-version +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/Dockerfile +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/LICENSE +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/Makefile +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/docs/ci-cd.md +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/docs/cli-reference.md +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/docs/development.md +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/docs/troubleshooting.md +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/examples/config/sarif-dashboard-parity.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/examples/config/sarif-dashboard-parity.toml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/examples/config/sarif-diff-ci-cd.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/examples/config/sarif-diff-ci-cd.toml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/examples/config/sarif-instance-detail.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/examples/config/sarif-instance-detail.toml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/instructions/gitlab-commit-status/uat.md +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/pytest.ini +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/scripts/build_container.sh +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/scripts/build_container_flexible.sh +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/scripts/deploy-test-docker.sh +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/scripts/deploy-test-pypi.sh +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/scripts/docker-entrypoint.sh +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/scripts/run.sh +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/session.md +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socket.yml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/alert_selection.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/classes.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/cli_client.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/exceptions.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/git_interface.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/helper/__init__.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/helper/socket_facts_loader.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/lazy_file_loader.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/logging.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/messages.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/resource_utils.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/scm/__init__.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/scm/base.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/scm/client.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/scm/github.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/scm/gitlab.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/scm_comments.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/socket_config.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/tools/reachability.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/core/utils.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/fossa_compat.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/output.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/plugins/__init__.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/plugins/base.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/plugins/formatters/__init__.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/plugins/formatters/slack.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/plugins/jira.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/plugins/manager.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/plugins/slack.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/plugins/teams.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/socketsecurity/plugins/webhook.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/__init__.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/core/conftest.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/core/create_diff_input.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/core/test_diff_alerts.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/core/test_diff_generation.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/core/test_has_manifest_files.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/core/test_package_and_alerts.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/core/test_supporting_methods.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/data/fullscans/create_response.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/data/fullscans/diff/stream_diff.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/data/fullscans/head_scan/metadata.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/data/fullscans/new_scan/metadata.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/data/repos/repo_info_error.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/data/repos/repo_info_no_head.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/data/repos/repo_info_success.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/data/settings/security-policy.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/e2e/fixtures/simple-npm/index.js +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/e2e/fixtures/simple-npm/package.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/e2e/fixtures/simple-pypi/requirements.txt +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/e2e/validate-gitlab.sh +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/e2e/validate-json.sh +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/e2e/validate-reachability.sh +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/e2e/validate-sarif.sh +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/e2e/validate-scan.sh +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/fixtures/fossa/README.md +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/fixtures/fossa/fossa-analyze-empty.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/fixtures/fossa/fossa-analyze-populated.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/fixtures/fossa/fossa-sbom-empty-deep.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/fixtures/fossa/fossa-sbom-populated.json +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/__init__.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_alert_selection.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_client.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_config.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_dependency_overview.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_disable_ignore.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_fossa_compat.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_fossa_parity.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_gitlab_auth.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_gitlab_auth_fallback.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_gitlab_commit_status.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_gitlab_format.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_ignore_telemetry_filtering.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_output.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/tests/unit/test_slack_plugin.py +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/workflows/bitbucket-pipelines.yml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/workflows/buildkite.yml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/workflows/github-actions.yml +0 -0
- {socketsecurity-2.2.93 → socketsecurity-2.3.1}/workflows/gitlab-ci.yml +0 -0
|
@@ -1,5 +1,78 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.3.1
|
|
4
|
+
|
|
5
|
+
### New: brotli-compressed `.socket.facts.json` upload
|
|
6
|
+
|
|
7
|
+
The reachability facts file (`.socket.facts.json`) is now brotli-compressed before it is
|
|
8
|
+
uploaded as part of a full scan. The Socket API transparently decompresses any multipart
|
|
9
|
+
part named exactly `.socket.facts.json.br` and stores it as plain `.socket.facts.json`, so
|
|
10
|
+
the stored result is unchanged — but the on-the-wire payload shrinks dramatically (a
|
|
11
|
+
~262 MB facts file compresses to roughly 15–30 MB).
|
|
12
|
+
|
|
13
|
+
This fixes large tier‑1 reachability scans that previously failed when the uncompressed
|
|
14
|
+
facts file exceeded the API's per‑file upload size cap (surfaced to the CLI as an HTTP
|
|
15
|
+
4xx/“502”, leaving the scan stuck with no report).
|
|
16
|
+
|
|
17
|
+
Details:
|
|
18
|
+
|
|
19
|
+
- Compression happens at the upload boundary (`Core.create_full_scan`); the file on disk is
|
|
20
|
+
left untouched, so local consumers (SARIF/JSON output, tier‑1 finalize, alert selection)
|
|
21
|
+
continue to read the plain `.socket.facts.json`.
|
|
22
|
+
- Only a file whose basename is exactly `.socket.facts.json` is compressed (the API matches
|
|
23
|
+
that exact name). A custom `--reach-output-file` name is uploaded uncompressed, as before.
|
|
24
|
+
- Empty baseline-scan placeholder files are not compressed.
|
|
25
|
+
- Compression never blocks an upload: if it fails for any reason it falls back to uploading
|
|
26
|
+
the plain file, and a partially-written `.socket.facts.json.br` is removed rather than
|
|
27
|
+
left behind in the target directory.
|
|
28
|
+
- Adds a `brotli` (CPython) / `brotlicffi` (PyPy) dependency.
|
|
29
|
+
|
|
30
|
+
## 2.3.0
|
|
31
|
+
|
|
32
|
+
### New: `--exit-code-on-api-error`
|
|
33
|
+
|
|
34
|
+
Adds a configurable exit code for API / infrastructure failures (timeouts,
|
|
35
|
+
network errors, unexpected exceptions), so CI pipelines can distinguish them
|
|
36
|
+
from blocking security findings (exit `1`):
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
socketcli --exit-code-on-api-error 100 ...
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Default is `3` (the code the CLI already used for these errors), so **default
|
|
43
|
+
behavior is unchanged** — the exit code only changes when you pass the flag.
|
|
44
|
+
Set it to a Buildkite `soft_fail` code, or to `0` to swallow infra errors.
|
|
45
|
+
|
|
46
|
+
**Interaction to be aware of:** `--disable-blocking` forces exit `0` for *all*
|
|
47
|
+
outcomes and therefore overrides `--exit-code-on-api-error`. Use the new flag
|
|
48
|
+
*without* `--disable-blocking` if you want a custom infra-error code to take
|
|
49
|
+
effect. See the exit-code reference in the README.
|
|
50
|
+
|
|
51
|
+
> A future `3.0` release is planned to make infrastructure errors exit non-zero
|
|
52
|
+
> even under `--disable-blocking` (so outages stop being silently swallowed).
|
|
53
|
+
> That is a breaking change and is intentionally **not** in this release.
|
|
54
|
+
|
|
55
|
+
### New: commit message auto-truncation
|
|
56
|
+
|
|
57
|
+
`--commit-message` values longer than 200 characters are now automatically
|
|
58
|
+
truncated before being sent to the API, preventing HTTP 413 errors from
|
|
59
|
+
oversized URL query parameters (common with AI-generated commit messages or
|
|
60
|
+
`$BUILDKITE_MESSAGE`).
|
|
61
|
+
|
|
62
|
+
### Improved: Buildkite log formatting
|
|
63
|
+
|
|
64
|
+
When running inside a Buildkite job (`BUILDKITE=true`), infrastructure errors
|
|
65
|
+
emit Buildkite log section markers (`^^^ +++` / `--- :warning:`) so the error
|
|
66
|
+
section auto-expands in the BK UI, plus a `soft_fail` hint. No effect on other
|
|
67
|
+
CI platforms.
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
|
|
71
|
+
- `--timeout` is now honored end-to-end: it was only applied to the local
|
|
72
|
+
`CliClient`, but the full-scan diff comparison uses the Socket SDK instance,
|
|
73
|
+
which was constructed without the CLI timeout and defaulted to 1200s.
|
|
74
|
+
- `--exclude-license-details` now propagates to the full-scan diff comparison
|
|
75
|
+
request (it was only applied to full-scan params / report URLs before).
|
|
3
76
|
## 2.2.93
|
|
4
77
|
|
|
5
78
|
- 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.
|
|
3
|
+
Version: 2.3.1
|
|
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>
|
|
@@ -33,6 +33,8 @@ Classifier: Intended Audience :: Developers
|
|
|
33
33
|
Classifier: Programming Language :: Python :: 3.11
|
|
34
34
|
Classifier: Programming Language :: Python :: 3.12
|
|
35
35
|
Requires-Python: >=3.11
|
|
36
|
+
Requires-Dist: brotli>=1.0.9; platform_python_implementation == 'CPython'
|
|
37
|
+
Requires-Dist: brotlicffi>=1.0.9; platform_python_implementation != 'CPython'
|
|
36
38
|
Requires-Dist: bs4>=0.0.2
|
|
37
39
|
Requires-Dist: gitpython
|
|
38
40
|
Requires-Dist: markdown>=3.10
|
|
@@ -252,6 +254,48 @@ Minimal pattern:
|
|
|
252
254
|
SOCKET_SECURITY_API_TOKEN: ${{ secrets.SOCKET_SECURITY_API_TOKEN }}
|
|
253
255
|
```
|
|
254
256
|
|
|
257
|
+
## Exit codes
|
|
258
|
+
|
|
259
|
+
| Code | Meaning |
|
|
260
|
+
|------|---------|
|
|
261
|
+
| `0` | Clean scan — no blocking issues (or `--disable-blocking` set) |
|
|
262
|
+
| `1` | Blocking security finding(s) detected |
|
|
263
|
+
| `2` | Scan interrupted (SIGINT / Ctrl+C) |
|
|
264
|
+
| `3` | Infrastructure or API error (timeout, network failure, unexpected error) |
|
|
265
|
+
|
|
266
|
+
`--exit-code-on-api-error <N>` remaps the infrastructure-error code (`3`) to any
|
|
267
|
+
value — e.g. a Buildkite `soft_fail` code, or `0` to swallow infra errors. Exit
|
|
268
|
+
`3` is a Socket convention, not an industry standard.
|
|
269
|
+
|
|
270
|
+
### How these options interact
|
|
271
|
+
|
|
272
|
+
The two flags that affect exit codes can cancel each other out, so the order of
|
|
273
|
+
precedence matters:
|
|
274
|
+
|
|
275
|
+
- **`--disable-blocking` wins over everything.** It forces exit `0` for *all*
|
|
276
|
+
outcomes — security findings *and* infrastructure errors. If you set it,
|
|
277
|
+
`--exit-code-on-api-error` has no effect (you'll always get `0`).
|
|
278
|
+
- **`--exit-code-on-api-error` only applies when `--disable-blocking` is *not*
|
|
279
|
+
set.** It changes the infra-error code (and the generic-error code); it never
|
|
280
|
+
touches the security-finding code (`1`).
|
|
281
|
+
|
|
282
|
+
So for the common "don't let Socket outages block my pipeline, but still fail on
|
|
283
|
+
real findings" goal, use `--exit-code-on-api-error` **without** `--disable-blocking`:
|
|
284
|
+
|
|
285
|
+
```yaml
|
|
286
|
+
# Buildkite: soft-fail only on infrastructure errors, still block on findings
|
|
287
|
+
steps:
|
|
288
|
+
- label: ":lock: Socket Security Scan"
|
|
289
|
+
command: "socketcli --exit-code-on-api-error 100 ..." # NOT --disable-blocking
|
|
290
|
+
soft_fail:
|
|
291
|
+
- exit_status: 100
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Combining `--disable-blocking` with `--exit-code-on-api-error 100` would make the
|
|
295
|
+
scan exit `0` on *both* findings and outages — the `soft_fail: 100` rule would
|
|
296
|
+
never match, and real findings would stop blocking. That's usually not what you
|
|
297
|
+
want.
|
|
298
|
+
|
|
255
299
|
## Common gotchas
|
|
256
300
|
|
|
257
301
|
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.
|
|
9
|
+
version = "2.3.1"
|
|
10
10
|
requires-python = ">= 3.11"
|
|
11
11
|
license = {"file" = "LICENSE"}
|
|
12
12
|
dependencies = [
|
|
@@ -19,6 +19,8 @@ dependencies = [
|
|
|
19
19
|
"socketdev>=3.0.33,<4.0.0",
|
|
20
20
|
"bs4>=0.0.2",
|
|
21
21
|
"markdown>=3.10",
|
|
22
|
+
"brotli>=1.0.9; platform_python_implementation == 'CPython'",
|
|
23
|
+
"brotlicffi>=1.0.9; platform_python_implementation != 'CPython'",
|
|
22
24
|
]
|
|
23
25
|
readme = "README.md"
|
|
24
26
|
description = "Socket Security CLI for CI/CD"
|
|
@@ -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",
|
|
@@ -51,6 +51,26 @@ _ALERT_TYPE_TITLE_OVERRIDES = {
|
|
|
51
51
|
|
|
52
52
|
_HUMANIZE_BOUNDARY = re.compile(r"(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])")
|
|
53
53
|
|
|
54
|
+
# Reachability facts-file upload compression.
|
|
55
|
+
#
|
|
56
|
+
# The Socket full-scan endpoint transparently brotli-decompresses any multipart part
|
|
57
|
+
# whose basename is exactly ``.socket.facts.json.br`` and stores it as plain
|
|
58
|
+
# ``.socket.facts.json``. Compressing the facts file on upload keeps it well under the
|
|
59
|
+
# server's per-file size cap (a ~262 MB facts file compresses to roughly 15-30 MB),
|
|
60
|
+
# which is required for large reachability (tier 1) scans to succeed.
|
|
61
|
+
#
|
|
62
|
+
# The server matches the *exact* name ``.socket.facts.json.br``, so we only compress
|
|
63
|
+
# files whose basename is exactly ``.socket.facts.json`` (a custom ``--reach-output-file``
|
|
64
|
+
# name would not be decompressed server-side, so it is left as a plain upload).
|
|
65
|
+
SOCKET_FACTS_FILENAME = ".socket.facts.json"
|
|
66
|
+
SOCKET_FACTS_BROTLI_FILENAME = ".socket.facts.json.br"
|
|
67
|
+
# Brotli quality (0-11); 5 is a good speed/ratio tradeoff for large JSON payloads.
|
|
68
|
+
SOCKET_FACTS_BROTLI_QUALITY = 5
|
|
69
|
+
# Largest brotli window (2**24 bytes); improves the ratio on large facts files.
|
|
70
|
+
SOCKET_FACTS_BROTLI_LGWIN = 24
|
|
71
|
+
# Stream the facts file in 1 MiB chunks so large files aren't held fully in memory.
|
|
72
|
+
SOCKET_FACTS_BROTLI_CHUNK_SIZE = 1024 * 1024
|
|
73
|
+
|
|
54
74
|
|
|
55
75
|
def _humanize_alert_type(alert_type: str) -> str:
|
|
56
76
|
"""Convert a camelCase/PascalCase alert type into a Title-Cased label.
|
|
@@ -544,6 +564,102 @@ class Core:
|
|
|
544
564
|
log.debug(f"Unable to finalize tier 1 scan: {e}")
|
|
545
565
|
return False
|
|
546
566
|
|
|
567
|
+
@staticmethod
|
|
568
|
+
def _compress_facts_file(source_path: str) -> str:
|
|
569
|
+
"""Brotli-compress a ``.socket.facts.json`` file to a sibling ``.socket.facts.json.br``.
|
|
570
|
+
|
|
571
|
+
The source is streamed in chunks so a large facts file (hundreds of MB) never has
|
|
572
|
+
to be held in memory at once. The compressed file is written next to the source so
|
|
573
|
+
that the multipart key the SDK derives keeps the same directory prefix, only with a
|
|
574
|
+
``.br`` basename. Any existing ``.socket.facts.json.br`` sibling is overwritten, and a
|
|
575
|
+
partially-written output is removed if compression fails part-way through (e.g. the
|
|
576
|
+
disk fills up mid-stream) so no orphaned ``.br`` is left in the target directory.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
source_path: Path to the plain ``.socket.facts.json`` file.
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
Path to the compressed sibling file.
|
|
583
|
+
"""
|
|
584
|
+
# Imported lazily so the dependency is only needed when actually uploading a facts
|
|
585
|
+
# file. brotlicffi is the API-compatible fallback used on PyPy / non-CPython runtimes.
|
|
586
|
+
try:
|
|
587
|
+
import brotli
|
|
588
|
+
except ImportError:
|
|
589
|
+
import brotlicffi as brotli
|
|
590
|
+
|
|
591
|
+
target_path = os.path.join(os.path.dirname(source_path), SOCKET_FACTS_BROTLI_FILENAME)
|
|
592
|
+
compressor = brotli.Compressor(
|
|
593
|
+
quality=SOCKET_FACTS_BROTLI_QUALITY,
|
|
594
|
+
lgwin=SOCKET_FACTS_BROTLI_LGWIN,
|
|
595
|
+
)
|
|
596
|
+
try:
|
|
597
|
+
with open(source_path, "rb") as src, open(target_path, "wb") as dst:
|
|
598
|
+
while True:
|
|
599
|
+
chunk = src.read(SOCKET_FACTS_BROTLI_CHUNK_SIZE)
|
|
600
|
+
if not chunk:
|
|
601
|
+
break
|
|
602
|
+
compressed = compressor.process(chunk)
|
|
603
|
+
if compressed:
|
|
604
|
+
dst.write(compressed)
|
|
605
|
+
dst.write(compressor.finish())
|
|
606
|
+
except BaseException:
|
|
607
|
+
# Don't leave a half-written .br behind for the caller to miss (it only tracks
|
|
608
|
+
# the path for cleanup once this returns). Remove it, then re-raise so the caller
|
|
609
|
+
# falls back to uploading the plain file.
|
|
610
|
+
try:
|
|
611
|
+
os.unlink(target_path)
|
|
612
|
+
except OSError:
|
|
613
|
+
pass
|
|
614
|
+
raise
|
|
615
|
+
return target_path
|
|
616
|
+
|
|
617
|
+
def _compress_facts_files_for_upload(self, files: List[str]) -> Tuple[List[str], List[str]]:
|
|
618
|
+
"""Replace any ``.socket.facts.json`` upload entry with a brotli-compressed ``.br`` sibling.
|
|
619
|
+
|
|
620
|
+
The Socket full-scan endpoint transparently decompresses a multipart part named
|
|
621
|
+
exactly ``.socket.facts.json.br``, so compressing here keeps a large facts file under
|
|
622
|
+
the server's per-file size cap without changing the stored result. Files whose
|
|
623
|
+
basename is not exactly ``.socket.facts.json`` are left untouched (the server only
|
|
624
|
+
matches that exact name), as are empty placeholder files (e.g. baseline scans).
|
|
625
|
+
|
|
626
|
+
Compression never blocks an upload: if it fails for any reason (missing optional
|
|
627
|
+
``brotli`` dependency, unwritable directory, etc.) the original plain file is used.
|
|
628
|
+
|
|
629
|
+
Args:
|
|
630
|
+
files: The list of file paths about to be uploaded.
|
|
631
|
+
|
|
632
|
+
Returns:
|
|
633
|
+
``(upload_files, temp_paths)`` where ``upload_files`` is the possibly-rewritten
|
|
634
|
+
list to upload and ``temp_paths`` are compressed files the caller must delete
|
|
635
|
+
once the upload completes.
|
|
636
|
+
"""
|
|
637
|
+
upload_files: List[str] = []
|
|
638
|
+
temp_paths: List[str] = []
|
|
639
|
+
for file_path in files:
|
|
640
|
+
try:
|
|
641
|
+
if (
|
|
642
|
+
os.path.basename(file_path) == SOCKET_FACTS_FILENAME
|
|
643
|
+
and os.path.isfile(file_path)
|
|
644
|
+
and os.path.getsize(file_path) > 0
|
|
645
|
+
):
|
|
646
|
+
compressed_path = self._compress_facts_file(file_path)
|
|
647
|
+
log.debug(
|
|
648
|
+
f"Brotli-compressed {file_path} for upload: "
|
|
649
|
+
f"{os.path.getsize(file_path)} -> {os.path.getsize(compressed_path)} bytes "
|
|
650
|
+
f"(uploading as {SOCKET_FACTS_BROTLI_FILENAME})"
|
|
651
|
+
)
|
|
652
|
+
upload_files.append(compressed_path)
|
|
653
|
+
temp_paths.append(compressed_path)
|
|
654
|
+
continue
|
|
655
|
+
except Exception as e:
|
|
656
|
+
# Never let compression break an upload: fall back to the plain file.
|
|
657
|
+
log.warning(
|
|
658
|
+
f"Failed to brotli-compress facts file {file_path}, uploading uncompressed: {e}"
|
|
659
|
+
)
|
|
660
|
+
upload_files.append(file_path)
|
|
661
|
+
return upload_files, temp_paths
|
|
662
|
+
|
|
547
663
|
def create_full_scan(self, files: List[str], params: FullScanParams, base_paths: Optional[List[str]] = None) -> FullScan:
|
|
548
664
|
"""
|
|
549
665
|
Creates a new full scan via the Socket API.
|
|
@@ -559,7 +675,19 @@ class Core:
|
|
|
559
675
|
log.info("Creating new full scan")
|
|
560
676
|
create_full_start = time.time()
|
|
561
677
|
|
|
562
|
-
|
|
678
|
+
# Brotli-compress the reachability facts file (if present) so it is uploaded as a
|
|
679
|
+
# `.socket.facts.json.br` part. The API decompresses it server-side, keeping a large
|
|
680
|
+
# facts file under the per-file upload size cap. See _compress_facts_files_for_upload.
|
|
681
|
+
upload_files, compressed_temp_files = self._compress_facts_files_for_upload(files)
|
|
682
|
+
try:
|
|
683
|
+
res = self.sdk.fullscans.post(upload_files, params, use_types=True, use_lazy_loading=True, max_open_files=50, base_paths=base_paths)
|
|
684
|
+
finally:
|
|
685
|
+
for temp_file in compressed_temp_files:
|
|
686
|
+
try:
|
|
687
|
+
os.unlink(temp_file)
|
|
688
|
+
log.debug(f"Cleaned up temporary compressed facts file: {temp_file}")
|
|
689
|
+
except OSError as cleanup_error:
|
|
690
|
+
log.debug(f"Failed to clean up temporary compressed facts file {temp_file}: {cleanup_error}")
|
|
563
691
|
if not res.success:
|
|
564
692
|
log.error(f"Error creating full scan: {res.message}, status: {res.status}")
|
|
565
693
|
raise Exception(f"Error creating full scan: {res.message}, status: {res.status}")
|
|
@@ -941,7 +1069,8 @@ class Core:
|
|
|
941
1069
|
def get_added_and_removed_packages(
|
|
942
1070
|
self,
|
|
943
1071
|
head_full_scan_id: str,
|
|
944
|
-
new_full_scan_id: str
|
|
1072
|
+
new_full_scan_id: str,
|
|
1073
|
+
include_license_details: bool = True
|
|
945
1074
|
) -> Tuple[Dict[str, Package], Dict[str, Package], Dict[str, Package]]:
|
|
946
1075
|
"""
|
|
947
1076
|
Get packages that were added and removed between scans.
|
|
@@ -958,12 +1087,12 @@ class Core:
|
|
|
958
1087
|
diff_start = time.time()
|
|
959
1088
|
try:
|
|
960
1089
|
diff_report = (
|
|
961
|
-
self.sdk.fullscans.stream_diff
|
|
962
|
-
(
|
|
1090
|
+
self.sdk.fullscans.stream_diff(
|
|
963
1091
|
self.config.org_slug,
|
|
964
1092
|
head_full_scan_id,
|
|
965
1093
|
new_full_scan_id,
|
|
966
|
-
use_types=True
|
|
1094
|
+
use_types=True,
|
|
1095
|
+
include_license_details=str(include_license_details).lower()
|
|
967
1096
|
).data
|
|
968
1097
|
)
|
|
969
1098
|
except APIFailure as e:
|
|
@@ -1175,7 +1304,11 @@ class Core:
|
|
|
1175
1304
|
added_packages,
|
|
1176
1305
|
removed_packages,
|
|
1177
1306
|
packages
|
|
1178
|
-
) = self.get_added_and_removed_packages(
|
|
1307
|
+
) = self.get_added_and_removed_packages(
|
|
1308
|
+
head_full_scan_id,
|
|
1309
|
+
new_full_scan.id,
|
|
1310
|
+
include_license_details=getattr(params, "include_license_details", True)
|
|
1311
|
+
)
|
|
1179
1312
|
|
|
1180
1313
|
# Separate unchanged packages from added/removed for --strict-blocking support
|
|
1181
1314
|
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(
|
|
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
|
-
|
|
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
|
|
174
|
+
timeout=get_api_request_timeout(config)
|
|
123
175
|
)
|
|
124
176
|
log.debug("loaded socket_config")
|
|
125
177
|
client = CliClient(socket_config)
|