From be07ad7cf77ed63cecbee1cc940e3e1e1d8bc6d8 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Fri, 29 May 2026 16:42:01 -0400 Subject: [PATCH 1/6] feat: add --exit-code-on-api-error flag + Buildkite-aware infra error logging Adds a configurable exit code for API/infrastructure failures so CI pipelines can distinguish them from blocking security findings (exit 1), without changing any default behavior. - New CliConfig field exit_code_on_api_error (default 3) + --exit-code-on-api-error flag. The CLI already exited 3 on unexpected errors; this just makes that code configurable (e.g. remap to a Buildkite soft_fail code, or 0 to swallow). - New _emit_infrastructure_error helper + IS_BUILDKITE gate: emits Buildkite log section markers (^^^ +++ / --- :warning:) and a soft_fail hint when running in Buildkite; plain log.error elsewhere so markers don't leak as literal text. - Wire the top-level generic-exception handler in cli() through the helper and the configurable code. Deliberately NON-breaking for 2.3.x: - --disable-blocking STILL forces exit 0 for all outcomes and takes precedence over --exit-code-on-api-error (documented in the flag help so the two aren't combined by mistake). - Default exit codes are unchanged; the exit code only changes when the user explicitly passes the flag. The breaking variant (infra errors bypassing --disable-blocking, distinct RequestTimeoutExceeded handling, exit 1 -> 3 for diff API failures) is intentionally deferred to a future 3.0 release. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- socketsecurity/config.py | 17 ++++++++++++++ socketsecurity/socketcli.py | 44 +++++++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/socketsecurity/config.py b/socketsecurity/config.py index c048af2..9b818a2 100644 --- a/socketsecurity/config.py +++ b/socketsecurity/config.py @@ -101,6 +101,7 @@ class CliConfig: pending_head: bool = False enable_diff: bool = False timeout: Optional[int] = 1200 + exit_code_on_api_error: int = 3 exclude_license_details: bool = False include_module_folders: bool = False repo_is_public: bool = False @@ -219,6 +220,7 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig': 'integration_type': args.integration, 'pending_head': args.pending_head, 'timeout': args.timeout, + 'exit_code_on_api_error': args.exit_code_on_api_error, 'exclude_license_details': args.exclude_license_details, 'include_module_folders': args.include_module_folders, 'repo_is_public': args.repo_is_public, @@ -802,6 +804,21 @@ def create_argument_parser() -> argparse.ArgumentParser: help="Timeout in seconds for API requests", required=False ) + advanced_group.add_argument( + "--exit-code-on-api-error", + dest="exit_code_on_api_error", + type=int, + default=3, + metavar="", + help=( + "Exit code to use when the CLI fails on an API or infrastructure error " + "(timeout, network failure, unexpected exception). Default: 3. Useful for " + "distinguishing infrastructure failures from security findings (exit 1) in " + "CI -- e.g. set to a Buildkite soft_fail code. NOTE: --disable-blocking " + "forces exit 0 for ALL outcomes and therefore overrides this flag; do not " + "combine the two if you want the custom code to take effect." + ) + ) advanced_group.add_argument( "--allow-unverified", action="store_true", diff --git a/socketsecurity/socketcli.py b/socketsecurity/socketcli.py index 26823cc..4ddebff 100644 --- a/socketsecurity/socketcli.py +++ b/socketsecurity/socketcli.py @@ -27,6 +27,37 @@ load_dotenv() +# Buildkite sets BUILDKITE=true in every job environment. Used to gate log +# section markers that would render as literal text on other CI platforms. +IS_BUILDKITE = os.getenv("BUILDKITE") == "true" + + +def _emit_infrastructure_error(message: str, include_traceback: bool = False) -> None: + """Emit a structured error for infrastructure/API failures. + + When running in Buildkite, wraps the error in log-section markers + (`^^^ +++` expands the section in the BK UI) and prints a soft_fail hint. + On every other platform it's a plain log.error so the markers don't leak + as literal text. This is presentation only -- it does not decide the exit + code (the caller does that, honoring --disable-blocking and + --exit-code-on-api-error). + """ + if IS_BUILDKITE: + print("^^^ +++", flush=True) + print("--- :warning: Socket infrastructure error", flush=True) + + log.error(message) + + if IS_BUILDKITE: + log.error( + "Tip: this is an infrastructure error, not a security finding. To keep it " + "from blocking the build, add a soft_fail rule for the CLI's API-error exit " + "code (default 3, or whatever you pass to --exit-code-on-api-error)." + ) + + if include_traceback: + traceback.print_exc() + def build_license_artifact_payload( diff: Diff, @@ -73,12 +104,17 @@ def cli(): else: sys.exit(0) except Exception as error: - log.error("Unexpected error when running the cli") - log.error(error) - traceback.print_exc() config = CliConfig.from_args() # Get current config + _emit_infrastructure_error( + f"Unexpected error when running the CLI: {error}", + include_traceback=True, + ) + # --disable-blocking forces a clean exit for ALL outcomes (it takes + # precedence over --exit-code-on-api-error); otherwise infra/API errors + # exit with the configurable code (default 3), keeping them distinct + # from blocking security findings (exit 1). if not config.disable_blocking: - sys.exit(3) + sys.exit(config.exit_code_on_api_error) else: sys.exit(0) From f3ad470ded28ae731da8f7d7f9f84f029b003f7b Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 18 May 2026 16:01:43 -0400 Subject: [PATCH 2/6] feat(config): auto-truncate commit messages over 200 chars The --commit-message flag passes its value directly into the API request URL as a query parameter with no length limit. AI-generated commit messages and the common CI pattern of concatenating $BUILDKITE_BUILD_NUMBER + $BUILDKITE_MESSAGE can easily exceed URL length limits, producing HTTP 413 errors. The 413 originates from an infrastructure-layer URL length limit (nginx/Cloudflare), not application-level validation -- confirmed via inspection of the Socket API route handler, which has no constraint on commit_message (unlike committers, which enforces <= 200 chars and returns a clean 400). 200 chars chosen as a conservative defensive ceiling given URL encoding can 2-3x raw character count. No customer should ever want a 2000-character commit message in their scan metadata. A backend-side validation (returning 400 instead of 413) is filed as a follow-on for the depscan API team. Motivated by customer incidents (Plaid). Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- socketsecurity/config.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/socketsecurity/config.py b/socketsecurity/config.py index 9b818a2..7a262de 100644 --- a/socketsecurity/config.py +++ b/socketsecurity/config.py @@ -183,6 +183,19 @@ def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig': if commit_message and commit_message.startswith('"') and commit_message.endswith('"'): commit_message = commit_message[1:-1] + # Truncate to avoid 413s from oversized URL query parameters. + # The API has no application-layer length validation on commit_message; + # the 413 originates from an infrastructure-layer URL length limit + # (nginx/Cloudflare). 200 chars chosen as a conservative ceiling given + # URL encoding can 2-3x raw character count. + MAX_COMMIT_MESSAGE_LENGTH = 200 + if commit_message and len(commit_message) > MAX_COMMIT_MESSAGE_LENGTH: + logging.debug( + f"commit_message truncated from {len(commit_message)} to " + f"{MAX_COMMIT_MESSAGE_LENGTH} characters to avoid API request size limits" + ) + commit_message = commit_message[:MAX_COMMIT_MESSAGE_LENGTH] + config_args = { 'api_token': api_token, 'repo': args.repo, From e671669e4f76d39481500438704a39b0a68e2d9e Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Mon, 4 May 2026 13:52:41 -0700 Subject: [PATCH 3/6] pass timeout through SDK diff requests Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- socketsecurity/socketcli.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/socketsecurity/socketcli.py b/socketsecurity/socketcli.py index 4ddebff..1849239 100644 --- a/socketsecurity/socketcli.py +++ b/socketsecurity/socketcli.py @@ -93,6 +93,23 @@ def _write_attribution_file(config, payload: dict) -> None: Core.save_file(config.license_file_name, json.dumps(payload, indent=2)) +DEFAULT_API_TIMEOUT = 1200 + + +def get_api_request_timeout(config: CliConfig) -> int: + return config.timeout if config.timeout is not None else DEFAULT_API_TIMEOUT + + +def build_socket_sdk(config: CliConfig) -> socketdev: + cli_user_agent_string = f"SocketPythonCLI/{config.version}" + return socketdev( + token=config.api_token, + timeout=get_api_request_timeout(config), + allow_unverified=config.allow_unverified, + user_agent=cli_user_agent_string + ) + + def cli(): try: main_code() @@ -135,8 +152,7 @@ def main_code(): "1. Command line: --api-token YOUR_TOKEN\n" "2. Environment variable: SOCKET_SECURITY_API_TOKEN") sys.exit(3) - cli_user_agent_string = f"SocketPythonCLI/{config.version}" - sdk = socketdev(token=config.api_token, allow_unverified=config.allow_unverified, user_agent=cli_user_agent_string) + sdk = build_socket_sdk(config) # Suppress urllib3 InsecureRequestWarning when using --allow-unverified if config.allow_unverified: @@ -155,7 +171,7 @@ def main_code(): socket_config = SocketConfig( api_key=config.api_token, allow_unverified_ssl=config.allow_unverified, - timeout=config.timeout if config.timeout is not None else 1200 # Use CLI timeout if provided + timeout=get_api_request_timeout(config) ) log.debug("loaded socket_config") client = CliClient(socket_config) From 7aacf82358982e6fae8d7d49191a0032883a6ba4 Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Fri, 29 May 2026 16:43:33 -0400 Subject: [PATCH 4/6] fix: propagate --exclude-license-details to the full-scan diff request The full-scan diff comparison ignored --exclude-license-details: the flag was applied to full-scan params and report URLs but never forwarded to the fullscans.stream_diff request, so diff comparisons always fetched license details regardless of the flag. Thread it through get_added_and_removed_packages -> stream_diff via a new include_license_details param (defaulting True to preserve current behavior). Non-breaking: the APIFailure handling at this call site is deliberately left as-is (exit 1, --disable-blocking -> 0). Re-routing diff APIFailures through the top-level exit-3 path is part of the 3.0 exit-code change, not this one. Originally from the unreleased PR #195 branch; the timeout-propagation half already landed in the preceding commit. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- socketsecurity/core/__init__.py | 15 ++++++++++----- tests/core/test_sdk_methods.py | 1 + 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/socketsecurity/core/__init__.py b/socketsecurity/core/__init__.py index 0a6b827..48bd283 100644 --- a/socketsecurity/core/__init__.py +++ b/socketsecurity/core/__init__.py @@ -920,7 +920,8 @@ def get_license_text_via_purl(self, packages: dict[str, Package], batch_size: in def get_added_and_removed_packages( self, head_full_scan_id: str, - new_full_scan_id: str + new_full_scan_id: str, + include_license_details: bool = True ) -> Tuple[Dict[str, Package], Dict[str, Package], Dict[str, Package]]: """ Get packages that were added and removed between scans. @@ -937,12 +938,12 @@ def get_added_and_removed_packages( diff_start = time.time() try: diff_report = ( - self.sdk.fullscans.stream_diff - ( + self.sdk.fullscans.stream_diff( self.config.org_slug, head_full_scan_id, new_full_scan_id, - use_types=True + use_types=True, + include_license_details=str(include_license_details).lower() ).data ) except APIFailure as e: @@ -1154,7 +1155,11 @@ def create_new_diff( added_packages, removed_packages, packages - ) = self.get_added_and_removed_packages(head_full_scan_id, new_full_scan.id) + ) = self.get_added_and_removed_packages( + head_full_scan_id, + new_full_scan.id, + include_license_details=getattr(params, "include_license_details", True) + ) # Separate unchanged packages from added/removed for --strict-blocking support unchanged_packages = { diff --git a/tests/core/test_sdk_methods.py b/tests/core/test_sdk_methods.py index bb096eb..fdcbef3 100644 --- a/tests/core/test_sdk_methods.py +++ b/tests/core/test_sdk_methods.py @@ -101,6 +101,7 @@ def test_get_added_and_removed_packages(core): "head", "new", use_types=True, + include_license_details="true", ) # Verify the results From 9867c9860a4cf60f7c28c745f6f4e13d0c1ae27a Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Fri, 29 May 2026 16:44:40 -0400 Subject: [PATCH 5/6] test: cover --exit-code-on-api-error, truncation, and Buildkite formatting tests/unit/test_cli_config.py - exit_code_on_api_error default 3 / custom / zero - commit-message truncation: passthrough under 200, truncate over 200, quote-strip-before-truncate tests/unit/test_socketcli.py - unexpected error exits 3 by default - --exit-code-on-api-error 100 remaps the failure exit code - --disable-blocking OVERRIDES --exit-code-on-api-error (-> 0): locks in the documented precedence so the soft_fail guidance can't silently regress - KeyboardInterrupt still exits 2 - _emit_infrastructure_error: BK markers + soft_fail hint only when IS_BUILDKITE; traceback gated on include_traceback Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- tests/unit/test_cli_config.py | 39 ++++++++++++++ tests/unit/test_socketcli.py | 96 +++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/tests/unit/test_cli_config.py b/tests/unit/test_cli_config.py index 27801ec..39447c2 100644 --- a/tests/unit/test_cli_config.py +++ b/tests/unit/test_cli_config.py @@ -1,6 +1,45 @@ import pytest from socketsecurity.config import CliConfig + +class TestExitCodeOnApiError: + def test_default_is_3(self): + config = CliConfig.from_args(["--api-token", "test"]) + assert config.exit_code_on_api_error == 3 + + def test_custom_value(self): + config = CliConfig.from_args( + ["--api-token", "test", "--exit-code-on-api-error", "100"] + ) + assert config.exit_code_on_api_error == 100 + + def test_zero_value(self): + config = CliConfig.from_args( + ["--api-token", "test", "--exit-code-on-api-error", "0"] + ) + assert config.exit_code_on_api_error == 0 + + +class TestCommitMessageTruncation: + def test_passes_through_under_limit(self): + msg = "a normal short commit message" + config = CliConfig.from_args(["--api-token", "test", "--commit-message", msg]) + assert config.commit_message == msg + + def test_truncated_above_limit(self): + config = CliConfig.from_args( + ["--api-token", "test", "--commit-message", "a" * 250] + ) + assert config.commit_message == "a" * 200 + + def test_quote_strip_runs_before_truncation(self): + quoted = '"' + ("b" * 250) + '"' + config = CliConfig.from_args( + ["--api-token", "test", "--commit-message", quoted] + ) + assert config.commit_message == "b" * 200 + + class TestCliConfig: def test_api_token_from_env(self, monkeypatch): monkeypatch.setenv("SOCKET_SECURITY_API_KEY", "test-token") diff --git a/tests/unit/test_socketcli.py b/tests/unit/test_socketcli.py index e48788a..39f59f5 100644 --- a/tests/unit/test_socketcli.py +++ b/tests/unit/test_socketcli.py @@ -1,7 +1,103 @@ +import sys + +import pytest + from socketsecurity.core.classes import Diff, Package +from socketsecurity import socketcli from socketsecurity.socketcli import build_license_artifact_payload +# --------------------------------------------------------------------------- +# Exit-code-on-api-error (flag-only, non-breaking for 2.3.x). +# +# Default behavior is unchanged from prior releases: unexpected errors exit 3, +# and --disable-blocking forces exit 0 for everything. The flag only changes +# the code when explicitly set, and --disable-blocking still takes precedence. +# --------------------------------------------------------------------------- + + +def _run_cli_expecting_exit(monkeypatch, argv, boom=None): + def fail_main_code(): + raise (boom or RuntimeError("infra boom")) + + monkeypatch.setattr(socketcli, "main_code", fail_main_code) + monkeypatch.setattr(sys, "argv", argv) + with pytest.raises(SystemExit) as exc_info: + socketcli.cli() + return exc_info.value.code + + +def test_unexpected_error_exits_3_by_default(monkeypatch): + code = _run_cli_expecting_exit(monkeypatch, ["socketcli", "--api-token", "test"]) + assert code == 3 + + +def test_exit_code_on_api_error_remaps_failure(monkeypatch): + code = _run_cli_expecting_exit( + monkeypatch, + ["socketcli", "--api-token", "test", "--exit-code-on-api-error", "100"], + ) + assert code == 100 + + +def test_disable_blocking_overrides_exit_code_on_api_error(monkeypatch): + # The documented interaction: --disable-blocking forces exit 0 for ALL + # outcomes and therefore overrides --exit-code-on-api-error. A user who + # sets both gets 0, NOT 100 -- this guards against silently regressing + # that precedence (which would break the documented soft_fail guidance). + code = _run_cli_expecting_exit( + monkeypatch, + [ + "socketcli", "--api-token", "test", + "--exit-code-on-api-error", "100", + "--disable-blocking", + ], + ) + assert code == 0 + + +def test_keyboard_interrupt_still_exits_2(monkeypatch): + code = _run_cli_expecting_exit( + monkeypatch, ["socketcli", "--api-token", "test"], boom=KeyboardInterrupt() + ) + assert code == 2 + + +# --------------------------------------------------------------------------- +# Buildkite-aware infrastructure error formatting. +# --------------------------------------------------------------------------- + + +def test_emit_infra_error_no_buildkite_has_no_markers(monkeypatch, capsys, caplog): + monkeypatch.setattr(socketcli, "IS_BUILDKITE", False) + with caplog.at_level("ERROR", logger="socketcli"): + socketcli._emit_infrastructure_error("something failed") + out = capsys.readouterr().out + assert "^^^ +++" not in out + assert "--- :warning:" not in out + assert "soft_fail" not in "\n".join(r.getMessage() for r in caplog.records) + + +def test_emit_infra_error_buildkite_emits_markers(monkeypatch, capsys, caplog): + monkeypatch.setattr(socketcli, "IS_BUILDKITE", True) + with caplog.at_level("ERROR", logger="socketcli"): + socketcli._emit_infrastructure_error("something failed") + out = capsys.readouterr().out + assert "^^^ +++" in out + assert "--- :warning: Socket infrastructure error" in out + assert "soft_fail" in "\n".join(r.getMessage() for r in caplog.records) + + +def test_emit_infra_error_traceback_gated(monkeypatch, capsys): + monkeypatch.setattr(socketcli, "IS_BUILDKITE", False) + try: + raise ValueError("boom") + except ValueError: + socketcli._emit_infrastructure_error("wrapped", include_traceback=True) + err = capsys.readouterr().err + assert "Traceback" in err and "ValueError: boom" in err + + def test_build_license_artifact_payload_without_packages_returns_empty_dict(): diff = Diff() From b1054e557c13f41bef786ca7d73ad13914a8099b Mon Sep 17 00:00:00 2001 From: lelia <2418071+lelia@users.noreply.github.com> Date: Fri, 29 May 2026 16:45:40 -0400 Subject: [PATCH 6/6] chore(release): 2.3.0 -- configurable API-error exit code Minor bump for the new --exit-code-on-api-error flag and the supporting non-breaking improvements (commit-message truncation, Buildkite-aware infra error logging, --timeout / --exclude-license-details fixes). This release is intentionally NON-breaking: default exit codes are unchanged, the exit code only shifts when --exit-code-on-api-error is explicitly passed, and --disable-blocking keeps its existing precedence. The breaking exit-code behavior change (infra errors exiting non-zero even under --disable-blocking) is deferred to a future 3.0. CHANGELOG + README document the flag AND its interaction with --disable-blocking (which overrides it) to reduce user error in the Buildkite soft_fail setup. Version refs synced across pyproject.toml, socketsecurity/__init__.py, and uv.lock (per the version-incrementation CI check). Signed-off-by: lelia <2418071+lelia@users.noreply.github.com> --- CHANGELOG.md | 47 ++++++++++++++++++++++++++++++++++++++ README.md | 42 ++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- socketsecurity/__init__.py | 2 +- uv.lock | 2 +- 5 files changed, 92 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ab4d97..ab44f75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,52 @@ # Changelog +## 2.3.0 + +### New: `--exit-code-on-api-error` + +Adds a configurable exit code for API / infrastructure failures (timeouts, +network errors, unexpected exceptions), so CI pipelines can distinguish them +from blocking security findings (exit `1`): + +``` +socketcli --exit-code-on-api-error 100 ... +``` + +Default is `3` (the code the CLI already used for these errors), so **default +behavior is unchanged** — the exit code only changes when you pass the flag. +Set it to a Buildkite `soft_fail` code, or to `0` to swallow infra errors. + +**Interaction to be aware of:** `--disable-blocking` forces exit `0` for *all* +outcomes and therefore overrides `--exit-code-on-api-error`. Use the new flag +*without* `--disable-blocking` if you want a custom infra-error code to take +effect. See the exit-code reference in the README. + +> A future `3.0` release is planned to make infrastructure errors exit non-zero +> even under `--disable-blocking` (so outages stop being silently swallowed). +> That is a breaking change and is intentionally **not** in this release. + +### New: commit message auto-truncation + +`--commit-message` values longer than 200 characters are now automatically +truncated before being sent to the API, preventing HTTP 413 errors from +oversized URL query parameters (common with AI-generated commit messages or +`$BUILDKITE_MESSAGE`). + +### Improved: Buildkite log formatting + +When running inside a Buildkite job (`BUILDKITE=true`), infrastructure errors +emit Buildkite log section markers (`^^^ +++` / `--- :warning:`) so the error +section auto-expands in the BK UI, plus a `soft_fail` hint. No effect on other +CI platforms. + +### Fixed + +- `--timeout` is now honored end-to-end: it was only applied to the local + `CliClient`, but the full-scan diff comparison uses the Socket SDK instance, + which was constructed without the CLI timeout and defaulted to 1200s. +- `--exclude-license-details` now propagates to the full-scan diff comparison + request (it was only applied to full-scan params / report URLs before). + ## 2.2.90 - Migrated license enrichment PURL lookup to the org-scoped endpoint (`POST /v0/orgs/{slug}/purl`) from the deprecated global endpoint (`POST /v0/purl`). diff --git a/README.md b/README.md index 66f042c..a3eeb10 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,48 @@ Minimal pattern: SOCKET_SECURITY_API_TOKEN: ${{ secrets.SOCKET_SECURITY_API_TOKEN }} ``` +## Exit codes + +| Code | Meaning | +|------|---------| +| `0` | Clean scan — no blocking issues (or `--disable-blocking` set) | +| `1` | Blocking security finding(s) detected | +| `2` | Scan interrupted (SIGINT / Ctrl+C) | +| `3` | Infrastructure or API error (timeout, network failure, unexpected error) | + +`--exit-code-on-api-error ` remaps the infrastructure-error code (`3`) to any +value — e.g. a Buildkite `soft_fail` code, or `0` to swallow infra errors. Exit +`3` is a Socket convention, not an industry standard. + +### How these options interact + +The two flags that affect exit codes can cancel each other out, so the order of +precedence matters: + +- **`--disable-blocking` wins over everything.** It forces exit `0` for *all* + outcomes — security findings *and* infrastructure errors. If you set it, + `--exit-code-on-api-error` has no effect (you'll always get `0`). +- **`--exit-code-on-api-error` only applies when `--disable-blocking` is *not* + set.** It changes the infra-error code (and the generic-error code); it never + touches the security-finding code (`1`). + +So for the common "don't let Socket outages block my pipeline, but still fail on +real findings" goal, use `--exit-code-on-api-error` **without** `--disable-blocking`: + +```yaml +# Buildkite: soft-fail only on infrastructure errors, still block on findings +steps: + - label: ":lock: Socket Security Scan" + command: "socketcli --exit-code-on-api-error 100 ..." # NOT --disable-blocking + soft_fail: + - exit_status: 100 +``` + +Combining `--disable-blocking` with `--exit-code-on-api-error 100` would make the +scan exit `0` on *both* findings and outages — the `soft_fail: 100` rule would +never match, and real findings would stop blocking. That's usually not what you +want. + ## Common gotchas See [`docs/troubleshooting.md`](https://github.com/SocketDev/socket-python-cli/blob/main/docs/troubleshooting.md#common-gotchas). diff --git a/pyproject.toml b/pyproject.toml index 137b98d..cf409eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" [project] name = "socketsecurity" -version = "2.2.91" +version = "2.3.0" requires-python = ">= 3.11" license = {"file" = "LICENSE"} dependencies = [ diff --git a/socketsecurity/__init__.py b/socketsecurity/__init__.py index 7fdf737..10f2993 100644 --- a/socketsecurity/__init__.py +++ b/socketsecurity/__init__.py @@ -1,3 +1,3 @@ __author__ = 'socket.dev' -__version__ = '2.2.91' +__version__ = '2.3.0' USER_AGENT = f'SocketPythonCLI/{__version__}' diff --git a/uv.lock b/uv.lock index e47a62c..ec4ab4f 100644 --- a/uv.lock +++ b/uv.lock @@ -1168,7 +1168,7 @@ wheels = [ [[package]] name = "socketsecurity" -version = "2.2.91" +version = "2.3.0" source = { editable = "." } dependencies = [ { name = "bs4" },