socketsecurity 2.4.0__tar.gz → 2.4.2__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 (142) hide show
  1. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/CHANGELOG.md +30 -0
  2. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/Dockerfile +23 -0
  3. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/PKG-INFO +1 -1
  4. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/pyproject.toml +1 -1
  5. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/__init__.py +1 -1
  6. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/config.py +36 -4
  7. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/__init__.py +40 -12
  8. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/tools/reachability.py +40 -6
  9. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/socketcli.py +18 -5
  10. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_config.py +49 -0
  11. socketsecurity-2.4.2/tests/unit/test_reachability.py +106 -0
  12. socketsecurity-2.4.2/tests/unit/test_tier1_finalize.py +70 -0
  13. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/uv.lock +1 -1
  14. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/CODEOWNERS +0 -0
  15. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
  16. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
  17. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
  18. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  19. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/actions/setup-docker/action.yml +0 -0
  20. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/actions/setup-hatch/action.yml +0 -0
  21. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/actions/setup-sfw/action.yml +0 -0
  22. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/dependabot.yml +0 -0
  23. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/workflows/dependency-review.yml +0 -0
  24. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/workflows/docker-stable.yml +0 -0
  25. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/workflows/e2e-test.yml +0 -0
  26. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/workflows/pr-preview.yml +0 -0
  27. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/workflows/python-tests.yml +0 -0
  28. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/workflows/release.yml +0 -0
  29. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/workflows/version-check.yml +0 -0
  30. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.github/zizmor.yml +0 -0
  31. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.gitignore +0 -0
  32. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.hooks/sync_version.py +0 -0
  33. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.pre-commit-config.yaml +0 -0
  34. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/.python-version +0 -0
  35. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/LICENSE +0 -0
  36. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/Makefile +0 -0
  37. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/README.md +0 -0
  38. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/docs/ci-cd.md +0 -0
  39. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/docs/cli-reference.md +0 -0
  40. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/docs/development.md +0 -0
  41. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/docs/troubleshooting.md +0 -0
  42. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/examples/config/sarif-dashboard-parity.json +0 -0
  43. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/examples/config/sarif-dashboard-parity.toml +0 -0
  44. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/examples/config/sarif-diff-ci-cd.json +0 -0
  45. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/examples/config/sarif-diff-ci-cd.toml +0 -0
  46. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/examples/config/sarif-instance-detail.json +0 -0
  47. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/examples/config/sarif-instance-detail.toml +0 -0
  48. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/instructions/gitlab-commit-status/uat.md +0 -0
  49. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/pytest.ini +0 -0
  50. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/scripts/build_container.sh +0 -0
  51. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/scripts/build_container_flexible.sh +0 -0
  52. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/scripts/deploy-test-docker.sh +0 -0
  53. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/scripts/deploy-test-pypi.sh +0 -0
  54. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/scripts/docker-entrypoint.sh +0 -0
  55. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/scripts/run.sh +0 -0
  56. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/session.md +0 -0
  57. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socket.yml +0 -0
  58. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/alert_selection.py +0 -0
  59. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/classes.py +0 -0
  60. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/cli_client.py +0 -0
  61. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/exceptions.py +0 -0
  62. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/git_interface.py +0 -0
  63. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/helper/__init__.py +0 -0
  64. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/helper/socket_facts_loader.py +0 -0
  65. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/lazy_file_loader.py +0 -0
  66. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/logging.py +0 -0
  67. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/messages.py +0 -0
  68. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/resource_utils.py +0 -0
  69. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/scm/__init__.py +0 -0
  70. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/scm/base.py +0 -0
  71. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/scm/client.py +0 -0
  72. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/scm/github.py +0 -0
  73. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/scm/gitlab.py +0 -0
  74. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/scm_comments.py +0 -0
  75. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/socket_config.py +0 -0
  76. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/core/utils.py +0 -0
  77. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/fossa_compat.py +0 -0
  78. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/output.py +0 -0
  79. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/plugins/__init__.py +0 -0
  80. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/plugins/base.py +0 -0
  81. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/plugins/formatters/__init__.py +0 -0
  82. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/plugins/formatters/slack.py +0 -0
  83. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/plugins/jira.py +0 -0
  84. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/plugins/manager.py +0 -0
  85. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/plugins/slack.py +0 -0
  86. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/plugins/teams.py +0 -0
  87. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/socketsecurity/plugins/webhook.py +0 -0
  88. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/__init__.py +0 -0
  89. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/core/conftest.py +0 -0
  90. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/core/create_diff_input.json +0 -0
  91. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/core/test_diff_alerts.py +0 -0
  92. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/core/test_diff_generation.py +0 -0
  93. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/core/test_facts_compression.py +0 -0
  94. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/core/test_has_manifest_files.py +0 -0
  95. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/core/test_package_and_alerts.py +0 -0
  96. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/core/test_sdk_methods.py +0 -0
  97. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/core/test_supporting_methods.py +0 -0
  98. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/data/fullscans/create_response.json +0 -0
  99. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/data/fullscans/diff/stream_diff.json +0 -0
  100. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
  101. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/data/fullscans/head_scan/metadata.json +0 -0
  102. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
  103. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
  104. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/data/fullscans/new_scan/metadata.json +0 -0
  105. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
  106. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/data/repos/repo_info_error.json +0 -0
  107. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/data/repos/repo_info_no_head.json +0 -0
  108. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/data/repos/repo_info_success.json +0 -0
  109. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/data/settings/security-policy.json +0 -0
  110. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/e2e/fixtures/simple-npm/index.js +0 -0
  111. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/e2e/fixtures/simple-npm/package.json +0 -0
  112. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/e2e/fixtures/simple-pypi/requirements.txt +0 -0
  113. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/e2e/validate-gitlab.sh +0 -0
  114. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/e2e/validate-json.sh +0 -0
  115. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/e2e/validate-reachability.sh +0 -0
  116. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/e2e/validate-sarif.sh +0 -0
  117. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/e2e/validate-scan.sh +0 -0
  118. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/fixtures/fossa/README.md +0 -0
  119. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/fixtures/fossa/fossa-analyze-empty.json +0 -0
  120. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/fixtures/fossa/fossa-analyze-populated.json +0 -0
  121. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/fixtures/fossa/fossa-sbom-empty-deep.json +0 -0
  122. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/fixtures/fossa/fossa-sbom-populated.json +0 -0
  123. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/__init__.py +0 -0
  124. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_alert_selection.py +0 -0
  125. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_cli_config.py +0 -0
  126. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_client.py +0 -0
  127. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_dependency_overview.py +0 -0
  128. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_disable_ignore.py +0 -0
  129. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_fossa_compat.py +0 -0
  130. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_fossa_parity.py +0 -0
  131. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_gitlab_auth.py +0 -0
  132. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_gitlab_auth_fallback.py +0 -0
  133. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_gitlab_commit_status.py +0 -0
  134. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_gitlab_format.py +0 -0
  135. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_ignore_telemetry_filtering.py +0 -0
  136. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_output.py +0 -0
  137. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_slack_plugin.py +0 -0
  138. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/tests/unit/test_socketcli.py +0 -0
  139. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/workflows/bitbucket-pipelines.yml +0 -0
  140. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/workflows/buildkite.yml +0 -0
  141. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/workflows/github-actions.yml +0 -0
  142. {socketsecurity-2.4.0 → socketsecurity-2.4.2}/workflows/gitlab-ci.yml +0 -0
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.4.2
4
+
5
+ ### Added: reachability flag and Coana environment alignment with the Node CLI
6
+
7
+ - New `--reach-disable-external-tool-checks` flag (passes `--disable-external-tool-checks`
8
+ to the Coana CLI).
9
+ - New `--reach-debug` flag to enable Coana debug output (`--debug`) independently of the
10
+ global `--enable-debug`.
11
+ - Node-style `--reach-analysis-timeout` and `--reach-analysis-memory-limit` are now the
12
+ primary flag names; the previous `--reach-timeout` / `--reach-memory-limit` continue to
13
+ work as hidden aliases.
14
+ - The Coana subprocess now receives `SOCKET_CLI_VERSION` and `SOCKET_CALLER_USER_AGENT` so
15
+ calls are attributed to the Python CLI. Proxies continue to work via the inherited
16
+ `HTTPS_PROXY` / `HTTP_PROXY` environment variables, which Coana reads itself.
17
+ - `SOCKET_REPO_NAME` / `SOCKET_BRANCH_NAME` are no longer forwarded to Coana when the repo
18
+ and branch are the default sentinels, avoiding cross-run reachability cache-bucket
19
+ collisions.
20
+ - Tier 1 reachability finalize now retries with exponential backoff instead of giving up on
21
+ the first transient error.
22
+
23
+ ## 2.4.1
24
+
25
+ ### Added: pyenv in the Docker image
26
+
27
+ - The `socketdev/cli` Docker image now bundles [pyenv](https://github.com/pyenv/pyenv)
28
+ (pinned to `v2.7.1`) along with the Alpine build dependencies needed to compile
29
+ CPython from source, so the image can build/install arbitrary Python versions on
30
+ demand.
31
+ - The CLI itself is unchanged — this release only affects the published Docker image.
32
+
3
33
  ## 2.4.0
4
34
 
5
35
  ### Changed: license details are no longer requested on the full-scan diff
@@ -88,6 +88,29 @@ ENV GOPATH="/go"
88
88
  # Install uv
89
89
  COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
90
90
 
91
+ # Install pyenv
92
+ # pyenv lets us build/install arbitrary Python versions on demand. We install
93
+ # the build dependencies needed to compile CPython on Alpine, then install
94
+ # pyenv itself. We deliberately only symlink the `pyenv` binary onto the PATH
95
+ # and do NOT add pyenv's shims directory, so its shims don't shadow the system
96
+ # Python that the CLI runs on.
97
+ RUN apk add --no-cache \
98
+ bash \
99
+ bzip2-dev \
100
+ ca-certificates \
101
+ libffi-dev \
102
+ libxslt-dev \
103
+ linux-headers \
104
+ ncurses-dev \
105
+ openssl-dev \
106
+ readline-dev \
107
+ sqlite-dev \
108
+ xz-dev \
109
+ zlib-dev
110
+ RUN curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | PYENV_GIT_TAG="v2.7.1" bash && \
111
+ ln -s ~/.pyenv/bin/pyenv /bin/pyenv && \
112
+ pyenv --version
113
+
91
114
  # Install CLI based on build mode
92
115
  RUN if [ "$USE_LOCAL_INSTALL" = "true" ]; then \
93
116
  echo "Using local development install"; \
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: socketsecurity
3
- Version: 2.4.0
3
+ Version: 2.4.2
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>
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "socketsecurity"
9
- version = "2.4.0"
9
+ version = "2.4.2"
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.4.0'
2
+ __version__ = '2.4.2'
3
3
  USER_AGENT = f'SocketPythonCLI/{__version__}'
@@ -139,6 +139,8 @@ class CliConfig:
139
139
  reach_continue_on_install_errors: bool = False
140
140
  reach_continue_on_missing_lock_files: bool = False
141
141
  reach_continue_on_no_source_files: bool = False
142
+ reach_debug: bool = False
143
+ reach_disable_external_tool_checks: bool = False
142
144
  max_purl_batch_size: int = 5000
143
145
  enable_commit_status: bool = False
144
146
  legal: bool = False
@@ -267,6 +269,8 @@ class CliConfig:
267
269
  'reach_continue_on_install_errors': args.reach_continue_on_install_errors,
268
270
  'reach_continue_on_missing_lock_files': args.reach_continue_on_missing_lock_files,
269
271
  'reach_continue_on_no_source_files': args.reach_continue_on_no_source_files,
272
+ 'reach_debug': args.reach_debug,
273
+ 'reach_disable_external_tool_checks': args.reach_disable_external_tool_checks,
270
274
  'max_purl_batch_size': args.max_purl_batch_size,
271
275
  'enable_commit_status': args.enable_commit_status,
272
276
  'legal': args.legal or args.legal_format == "fossa",
@@ -878,18 +882,32 @@ def create_argument_parser() -> argparse.ArgumentParser:
878
882
  help="Specific version of @coana-tech/cli to use (e.g., '1.2.3')"
879
883
  )
880
884
  reachability_group.add_argument(
881
- "--reach-timeout",
885
+ "--reach-analysis-timeout",
882
886
  dest="reach_analysis_timeout",
883
887
  type=int,
884
888
  metavar="<seconds>",
885
889
  help="Timeout for reachability analysis in seconds"
886
890
  )
891
+ # Backwards-compatible alias for the pre-alignment name. Kept working, hidden from help.
887
892
  reachability_group.add_argument(
888
- "--reach-memory-limit",
893
+ "--reach-timeout",
894
+ dest="reach_analysis_timeout",
895
+ type=int,
896
+ help=argparse.SUPPRESS
897
+ )
898
+ reachability_group.add_argument(
899
+ "--reach-analysis-memory-limit",
889
900
  dest="reach_analysis_memory_limit",
890
901
  type=int,
891
902
  metavar="<mb>",
892
- help="Memory limit for reachability analysis in MB"
903
+ help="Memory limit for reachability analysis in MB (defaults to the coana CLI's own default, currently 8192)"
904
+ )
905
+ # Backwards-compatible alias for the pre-alignment name. Kept working, hidden from help.
906
+ reachability_group.add_argument(
907
+ "--reach-memory-limit",
908
+ dest="reach_analysis_memory_limit",
909
+ type=int,
910
+ help=argparse.SUPPRESS
893
911
  )
894
912
  reachability_group.add_argument(
895
913
  "--reach-ecosystems",
@@ -957,7 +975,7 @@ def create_argument_parser() -> argparse.ArgumentParser:
957
975
  dest="reach_concurrency",
958
976
  type=int,
959
977
  metavar="<number>",
960
- help="Concurrency level for reachability analysis (must be >= 1)"
978
+ help="Concurrency level for reachability analysis (must be >= 1; defaults to the coana CLI's own default, currently 1)"
961
979
  )
962
980
  reachability_group.add_argument(
963
981
  "--reach-additional-params",
@@ -1002,6 +1020,20 @@ def create_argument_parser() -> argparse.ArgumentParser:
1002
1020
  action="store_true",
1003
1021
  help=argparse.SUPPRESS
1004
1022
  )
1023
+ reachability_group.add_argument(
1024
+ "--reach-debug",
1025
+ dest="reach_debug",
1026
+ action="store_true",
1027
+ help="Enable debug output for the reachability analysis (passes --debug to the coana CLI). "
1028
+ "Independent of the global --enable-debug flag."
1029
+ )
1030
+ reachability_group.add_argument(
1031
+ "--reach-disable-external-tool-checks",
1032
+ dest="reach_disable_external_tool_checks",
1033
+ action="store_true",
1034
+ help="Disable coana's external tool availability checks during reachability analysis "
1035
+ "(passes --disable-external-tool-checks to the coana CLI)."
1036
+ )
1005
1037
 
1006
1038
  parser.add_argument(
1007
1039
  '--version',
@@ -71,6 +71,11 @@ SOCKET_FACTS_BROTLI_LGWIN = 24
71
71
  # Stream the facts file in 1 MiB chunks so large files aren't held fully in memory.
72
72
  SOCKET_FACTS_BROTLI_CHUNK_SIZE = 1024 * 1024
73
73
 
74
+ # Tier 1 reachability finalize retry policy. The finalize call links the tier1 scan to the
75
+ # full scan and can fail transiently (network/API blips); a few backoff retries make it robust.
76
+ TIER1_FINALIZE_MAX_ATTEMPTS = 3
77
+ TIER1_FINALIZE_BACKOFF_SECONDS = 1.0
78
+
74
79
 
75
80
  def _humanize_alert_type(alert_type: str) -> str:
76
81
  """Convert a camelCase/PascalCase alert type into a Title-Cased label.
@@ -549,20 +554,43 @@ class Core:
549
554
  log.debug(f"Failed to read tier1ReachabilityScanId from {facts_file_path}: {e}")
550
555
  return False
551
556
 
552
- # Call the SDK to finalize the tier 1 scan
553
- try:
554
- success = self.sdk.fullscans.finalize_tier1(
555
- full_scan_id=full_scan_id,
556
- tier1_reachability_scan_id=tier1_scan_id,
557
- )
557
+ # Call the SDK to finalize the tier 1 scan, retrying transient failures with backoff.
558
+ last_error: Optional[Exception] = None
559
+ for attempt in range(1, TIER1_FINALIZE_MAX_ATTEMPTS + 1):
560
+ try:
561
+ success = self.sdk.fullscans.finalize_tier1(
562
+ full_scan_id=full_scan_id,
563
+ tier1_reachability_scan_id=tier1_scan_id,
564
+ )
558
565
 
559
- if success:
560
- log.debug(f"Successfully finalized tier 1 scan {tier1_scan_id} for full scan {full_scan_id}")
561
- return success
566
+ if success:
567
+ log.debug(f"Successfully finalized tier 1 scan {tier1_scan_id} for full scan {full_scan_id}")
568
+ return True
562
569
 
563
- except Exception as e:
564
- log.debug(f"Unable to finalize tier 1 scan: {e}")
565
- return False
570
+ log.debug(
571
+ f"finalize_tier1 returned a falsy result for scan {tier1_scan_id} "
572
+ f"(attempt {attempt}/{TIER1_FINALIZE_MAX_ATTEMPTS})"
573
+ )
574
+ except Exception as e:
575
+ last_error = e
576
+ log.debug(
577
+ f"Unable to finalize tier 1 scan (attempt {attempt}/{TIER1_FINALIZE_MAX_ATTEMPTS}): {e}"
578
+ )
579
+
580
+ if attempt < TIER1_FINALIZE_MAX_ATTEMPTS:
581
+ time.sleep(TIER1_FINALIZE_BACKOFF_SECONDS * (2 ** (attempt - 1)))
582
+
583
+ if last_error is not None:
584
+ log.debug(
585
+ f"Giving up finalizing tier 1 scan {tier1_scan_id} after "
586
+ f"{TIER1_FINALIZE_MAX_ATTEMPTS} attempts: {last_error}"
587
+ )
588
+ else:
589
+ log.debug(
590
+ f"Giving up finalizing tier 1 scan {tier1_scan_id} after "
591
+ f"{TIER1_FINALIZE_MAX_ATTEMPTS} attempts"
592
+ )
593
+ return False
566
594
 
567
595
  @staticmethod
568
596
  def _compress_facts_file(source_path: str) -> str:
@@ -1,15 +1,31 @@
1
1
  from socketdev import socketdev
2
2
  from typing import List, Optional, Dict, Any
3
3
  import os
4
+ import platform
4
5
  import subprocess
5
6
  import json
6
7
  import pathlib
7
8
  import logging
8
9
  import sys
9
10
 
11
+ from socketsecurity import __version__
12
+
10
13
  log = logging.getLogger(__name__)
11
14
 
12
15
 
16
+ def _build_caller_user_agent() -> str:
17
+ """Build the SOCKET_CALLER_USER_AGENT string forwarded to the coana CLI.
18
+
19
+ Mirrors the Node CLI's ``<product>/<version> <runtime>/<version> <platform>/<arch>``
20
+ shape so the backend can attribute reachability calls to the Python CLI.
21
+ """
22
+ return (
23
+ f"socket/{__version__} "
24
+ f"python/{platform.python_version()} "
25
+ f"{platform.system().lower()}/{platform.machine().lower()}"
26
+ )
27
+
28
+
13
29
  class ReachabilityAnalyzer:
14
30
  def __init__(self, sdk: socketdev, api_token: str):
15
31
  self.sdk = sdk
@@ -108,6 +124,8 @@ class ReachabilityAnalyzer:
108
124
  continue_on_install_errors: bool = False,
109
125
  continue_on_missing_lock_files: bool = False,
110
126
  continue_on_no_source_files: bool = False,
127
+ reach_debug: bool = False,
128
+ disable_external_tool_checks: bool = False,
111
129
  ) -> Dict[str, Any]:
112
130
  """
113
131
  Run reachability analysis.
@@ -147,8 +165,7 @@ class ReachabilityAnalyzer:
147
165
 
148
166
  # Add required arguments
149
167
  output_dir = str(pathlib.Path(output_path).parent)
150
- log.warning(f"output_dir: {output_dir}")
151
- log.warning(f"output_path: {output_path}")
168
+ log.debug(f"output_dir: {output_dir}, output_path: {output_path}")
152
169
  cmd.extend([
153
170
  "--output-dir", output_dir,
154
171
  "--socket-mode", output_path,
@@ -197,6 +214,12 @@ class ReachabilityAnalyzer:
197
214
  if enable_debug:
198
215
  cmd.append("-d")
199
216
 
217
+ if reach_debug:
218
+ cmd.append("--debug")
219
+
220
+ if disable_external_tool_checks:
221
+ cmd.append("--disable-external-tool-checks")
222
+
200
223
  if use_only_pregenerated_sboms:
201
224
  cmd.append("--use-only-pregenerated-sboms")
202
225
 
@@ -222,14 +245,25 @@ class ReachabilityAnalyzer:
222
245
  # Required environment variables for Coana CLI
223
246
  env["SOCKET_ORG_SLUG"] = org_slug
224
247
  env["SOCKET_CLI_API_TOKEN"] = self.api_token
225
-
226
- # Optional environment variables
248
+
249
+ # Identify the calling CLI to the coana tool / backend (parity with the Node CLI).
250
+ env["SOCKET_CLI_VERSION"] = __version__
251
+ env["SOCKET_CALLER_USER_AGENT"] = _build_caller_user_agent()
252
+
253
+ # NOTE: no proxy env is set here. coana already reads HTTPS_PROXY/HTTP_PROXY itself, and
254
+ # we pass the full parent env above, so it inherits them. A SOCKET_CLI_API_PROXY override
255
+ # should only be set from an explicit --proxy flag (not yet implemented), since seeding it
256
+ # from HTTPS_PROXY would be a no-op (it's the same value coana already resolves).
257
+
258
+ # Optional environment variables.
259
+ # NOTE: repo/branch are intentionally omitted by the caller (passed as None) when they
260
+ # are the default sentinels, to avoid polluting coana's per-repo/branch cache buckets.
227
261
  if repo_name:
228
262
  env["SOCKET_REPO_NAME"] = repo_name
229
-
263
+
230
264
  if branch_name:
231
265
  env["SOCKET_BRANCH_NAME"] = branch_name
232
-
266
+
233
267
  # Set NODE_TLS_REJECT_UNAUTHORIZED=0 if allow_unverified is True
234
268
  if allow_unverified:
235
269
  env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0"
@@ -95,6 +95,12 @@ def _write_attribution_file(config, payload: dict) -> None:
95
95
 
96
96
  DEFAULT_API_TIMEOUT = 1200
97
97
 
98
+ # Sentinel repo/branch names used when none can be detected from git or supplied via flags.
99
+ # When the repo/branch are these defaults we skip forwarding SOCKET_REPO_NAME/SOCKET_BRANCH_NAME
100
+ # to the coana CLI so unrelated default-named runs don't share reachability cache buckets.
101
+ DEFAULT_REPO_NAME = "socket-default-repo"
102
+ DEFAULT_BRANCH_NAME = "socket-default-branch"
103
+
98
104
 
99
105
  def get_api_request_timeout(config: CliConfig) -> int:
100
106
  return config.timeout if config.timeout is not None else DEFAULT_API_TIMEOUT
@@ -288,16 +294,21 @@ def main_code():
288
294
  except NoSuchPathError:
289
295
  raise Exception(f"Unable to find path {config.target_path}")
290
296
 
297
+ # Track whether repo/branch fell back to the default sentinels so reachability can skip
298
+ # forwarding them as coana cache-bucket keys (computed before any workspace suffixing).
299
+ repo_defaulted = not config.repo
300
+ branch_defaulted = not config.branch
301
+
291
302
  if not config.repo:
292
- base_repo_name = "socket-default-repo"
303
+ base_repo_name = DEFAULT_REPO_NAME
293
304
  if config.workspace_name:
294
305
  config.repo = f"{base_repo_name}-{config.workspace_name}"
295
306
  else:
296
307
  config.repo = base_repo_name
297
308
  log.debug(f"Using default repository name: {config.repo}")
298
-
309
+
299
310
  if not config.branch:
300
- config.branch = "socket-default-branch"
311
+ config.branch = DEFAULT_BRANCH_NAME
301
312
  log.debug(f"Using default branch name: {config.branch}")
302
313
 
303
314
  # Calculate the scan paths - combine target_path with sub_paths if provided
@@ -384,8 +395,8 @@ def main_code():
384
395
  enable_analysis_splitting=config.reach_enable_analysis_splitting or False,
385
396
  detailed_analysis_log_file=config.reach_detailed_analysis_log_file or False,
386
397
  lazy_mode=config.reach_lazy_mode or False,
387
- repo_name=config.repo,
388
- branch_name=config.branch,
398
+ repo_name=None if repo_defaulted else config.repo,
399
+ branch_name=None if branch_defaulted else config.branch,
389
400
  version=config.reach_version,
390
401
  concurrency=config.reach_concurrency,
391
402
  additional_params=config.reach_additional_params,
@@ -396,6 +407,8 @@ def main_code():
396
407
  continue_on_install_errors=config.reach_continue_on_install_errors,
397
408
  continue_on_missing_lock_files=config.reach_continue_on_missing_lock_files,
398
409
  continue_on_no_source_files=config.reach_continue_on_no_source_files,
410
+ reach_debug=config.reach_debug,
411
+ disable_external_tool_checks=config.reach_disable_external_tool_checks,
399
412
  )
400
413
 
401
414
  log.info("Reachability analysis completed successfully")
@@ -166,6 +166,55 @@ class TestCliConfigValidation:
166
166
  assert config.sarif_reachability == "reachable"
167
167
 
168
168
 
169
+ class TestReachAlignmentFlags:
170
+ """Tests for the reachability flag/default alignment with the Node CLI."""
171
+
172
+ BASE_ARGS = ["--api-token", "test-token", "--repo", "test-repo"]
173
+
174
+ def test_reach_defaults_are_unset_and_delegated_to_coana(self):
175
+ """memory-limit/concurrency/timeout are not hardcoded; omitted so coana applies its
176
+ own defaults (8192 MB / concurrency 1 / 600s), which already match what we'd set."""
177
+ config = CliConfig.from_args(self.BASE_ARGS + ["--reach"])
178
+ assert config.reach_analysis_memory_limit is None
179
+ assert config.reach_concurrency is None
180
+ assert config.reach_analysis_timeout is None
181
+
182
+ def test_reach_node_style_name_aliases(self):
183
+ """G8: Node-style primary names map to the same dests."""
184
+ config = CliConfig.from_args(
185
+ self.BASE_ARGS
186
+ + ["--reach", "--reach-analysis-timeout", "300", "--reach-analysis-memory-limit", "2048"]
187
+ )
188
+ assert config.reach_analysis_timeout == 300
189
+ assert config.reach_analysis_memory_limit == 2048
190
+
191
+ def test_reach_legacy_name_aliases_still_work(self):
192
+ """G8: pre-alignment names keep working (hidden aliases)."""
193
+ config = CliConfig.from_args(
194
+ self.BASE_ARGS + ["--reach", "--reach-timeout", "111", "--reach-memory-limit", "512"]
195
+ )
196
+ assert config.reach_analysis_timeout == 111
197
+ assert config.reach_analysis_memory_limit == 512
198
+
199
+ def test_reach_debug_flag(self):
200
+ """G9: dedicated --reach-debug flag, independent of --enable-debug."""
201
+ config = CliConfig.from_args(self.BASE_ARGS + ["--reach", "--reach-debug"])
202
+ assert config.reach_debug is True
203
+ assert config.enable_debug is False
204
+
205
+ def test_reach_disable_external_tool_checks_flag(self):
206
+ """G1: --reach-disable-external-tool-checks parses to its dest."""
207
+ config = CliConfig.from_args(
208
+ self.BASE_ARGS + ["--reach", "--reach-disable-external-tool-checks"]
209
+ )
210
+ assert config.reach_disable_external_tool_checks is True
211
+
212
+ def test_reach_new_flags_default_false(self):
213
+ config = CliConfig.from_args(self.BASE_ARGS + ["--reach"])
214
+ assert config.reach_debug is False
215
+ assert config.reach_disable_external_tool_checks is False
216
+
217
+
169
218
  def test_pyproject_requires_python_matches_tomllib_usage():
170
219
  pyproject = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))
171
220
  requires_python = pyproject["project"]["requires-python"]
@@ -0,0 +1,106 @@
1
+ """Tests for the reachability coana-CLI command/env construction (Node alignment).
2
+
3
+ These cover the arg-builder and environment wiring in
4
+ ``socketsecurity.core.tools.reachability.ReachabilityAnalyzer`` without actually
5
+ invoking npm/npx/coana: ``_ensure_coana_cli_installed`` and ``subprocess.run`` are mocked.
6
+ """
7
+ from unittest.mock import MagicMock
8
+
9
+ import pytest
10
+
11
+ from socketsecurity import __version__
12
+ from socketsecurity.core.tools import reachability
13
+ from socketsecurity.core.tools.reachability import (
14
+ ReachabilityAnalyzer,
15
+ _build_caller_user_agent,
16
+ )
17
+
18
+
19
+ @pytest.fixture
20
+ def analyzer():
21
+ return ReachabilityAnalyzer(MagicMock(), "test-api-token")
22
+
23
+
24
+ def _run(analyzer, mocker, **kwargs):
25
+ """Invoke run_reachability_analysis with npm/npx/coana mocked; return (cmd, env)."""
26
+ mocker.patch.object(analyzer, "_ensure_coana_cli_installed", return_value="@coana-tech/cli")
27
+ mocker.patch.object(analyzer, "_extract_scan_id", return_value="scan-123")
28
+ completed = MagicMock()
29
+ completed.returncode = 0
30
+ run_mock = mocker.patch.object(reachability.subprocess, "run", return_value=completed)
31
+
32
+ analyzer.run_reachability_analysis(org_slug="my-org", target_directory=".", **kwargs)
33
+
34
+ cmd = run_mock.call_args.args[0]
35
+ env = run_mock.call_args.kwargs["env"]
36
+ return cmd, env
37
+
38
+
39
+ def test_build_caller_user_agent_shape():
40
+ ua = _build_caller_user_agent()
41
+ parts = ua.split(" ")
42
+ assert parts[0] == f"socket/{__version__}"
43
+ assert parts[1].startswith("python/")
44
+ assert "/" in parts[2] # platform/arch
45
+
46
+
47
+ def test_reach_debug_appends_debug_long_flag(analyzer, mocker):
48
+ """G9: --reach-debug -> coana --debug; does not emit the global -d."""
49
+ cmd, _ = _run(analyzer, mocker, reach_debug=True)
50
+ assert "--debug" in cmd
51
+ assert "-d" not in cmd
52
+
53
+
54
+ def test_enable_debug_still_emits_short_d(analyzer, mocker):
55
+ """G9: existing global --enable-debug -> -d behavior is unchanged."""
56
+ cmd, _ = _run(analyzer, mocker, enable_debug=True)
57
+ assert "-d" in cmd
58
+ assert "--debug" not in cmd
59
+
60
+
61
+ def test_disable_external_tool_checks(analyzer, mocker):
62
+ """G1: --reach-disable-external-tool-checks -> coana --disable-external-tool-checks."""
63
+ cmd, _ = _run(analyzer, mocker, disable_external_tool_checks=True)
64
+ assert "--disable-external-tool-checks" in cmd
65
+
66
+ cmd2, _ = _run(analyzer, mocker)
67
+ assert "--disable-external-tool-checks" not in cmd2
68
+
69
+
70
+ def test_concurrency_and_memory_args(analyzer, mocker):
71
+ """G7: explicit concurrency/memory propagate as coana args."""
72
+ cmd, _ = _run(analyzer, mocker, concurrency=1, memory_limit=8192)
73
+ assert "--concurrency" in cmd and cmd[cmd.index("--concurrency") + 1] == "1"
74
+ assert "--memory-limit" in cmd and cmd[cmd.index("--memory-limit") + 1] == "8192"
75
+
76
+
77
+ def test_env_identifies_python_cli(analyzer, mocker):
78
+ """G5: SOCKET_CLI_VERSION + SOCKET_CALLER_USER_AGENT forwarded to coana."""
79
+ _, env = _run(analyzer, mocker)
80
+ assert env["SOCKET_CLI_VERSION"] == __version__
81
+ assert env["SOCKET_CALLER_USER_AGENT"].startswith("socket/")
82
+ assert env["SOCKET_ORG_SLUG"] == "my-org"
83
+ assert env["SOCKET_CLI_API_TOKEN"] == "test-api-token"
84
+
85
+
86
+ def test_no_proxy_env_set_by_default(analyzer, mocker, monkeypatch):
87
+ """coana inherits HTTPS_PROXY/HTTP_PROXY from the passed env; we don't set
88
+ SOCKET_CLI_API_PROXY ourselves (that's reserved for a future explicit --proxy flag)."""
89
+ monkeypatch.delenv("SOCKET_CLI_API_PROXY", raising=False)
90
+ monkeypatch.setenv("HTTPS_PROXY", "http://envproxy:3128")
91
+ _, env = _run(analyzer, mocker)
92
+ # Even with HTTPS_PROXY set, we don't copy it into SOCKET_CLI_API_PROXY (coana reads it itself).
93
+ assert "SOCKET_CLI_API_PROXY" not in env
94
+
95
+
96
+ def test_repo_branch_env_present_when_supplied(analyzer, mocker):
97
+ _, env = _run(analyzer, mocker, repo_name="acme/widget", branch_name="main")
98
+ assert env["SOCKET_REPO_NAME"] == "acme/widget"
99
+ assert env["SOCKET_BRANCH_NAME"] == "main"
100
+
101
+
102
+ def test_repo_branch_env_absent_when_none(analyzer, mocker):
103
+ """G6: caller passes None for default sentinels -> env keys omitted (cache hygiene)."""
104
+ _, env = _run(analyzer, mocker, repo_name=None, branch_name=None)
105
+ assert "SOCKET_REPO_NAME" not in env
106
+ assert "SOCKET_BRANCH_NAME" not in env
@@ -0,0 +1,70 @@
1
+ """Tests for tier1 reachability finalize retry/backoff (G11, Node parity)."""
2
+ import json
3
+ from unittest.mock import MagicMock
4
+
5
+ import pytest
6
+
7
+ from socketsecurity.core import TIER1_FINALIZE_MAX_ATTEMPTS, Core
8
+
9
+
10
+ @pytest.fixture
11
+ def core_with_mock_sdk():
12
+ # Build a Core without running org setup; we only exercise finalize_tier1_scan.
13
+ core = Core.__new__(Core)
14
+ core.sdk = MagicMock()
15
+ return core
16
+
17
+
18
+ @pytest.fixture
19
+ def facts_file(tmp_path):
20
+ path = tmp_path / ".socket.facts.json"
21
+ path.write_text(json.dumps({"tier1ReachabilityScanId": "tier1-abc"}), encoding="utf-8")
22
+ return str(path)
23
+
24
+
25
+ @pytest.fixture(autouse=True)
26
+ def no_sleep(mocker):
27
+ return mocker.patch("socketsecurity.core.time.sleep")
28
+
29
+
30
+ def test_finalize_succeeds_first_try(core_with_mock_sdk, facts_file, no_sleep):
31
+ core_with_mock_sdk.sdk.fullscans.finalize_tier1.return_value = True
32
+
33
+ assert core_with_mock_sdk.finalize_tier1_scan("full-1", facts_file) is True
34
+ assert core_with_mock_sdk.sdk.fullscans.finalize_tier1.call_count == 1
35
+ no_sleep.assert_not_called()
36
+
37
+
38
+ def test_finalize_retries_then_succeeds(core_with_mock_sdk, facts_file, no_sleep):
39
+ core_with_mock_sdk.sdk.fullscans.finalize_tier1.side_effect = [
40
+ Exception("transient"),
41
+ Exception("transient"),
42
+ True,
43
+ ]
44
+
45
+ assert core_with_mock_sdk.finalize_tier1_scan("full-1", facts_file) is True
46
+ assert core_with_mock_sdk.sdk.fullscans.finalize_tier1.call_count == 3
47
+ assert no_sleep.call_count == 2 # backoff between the 3 attempts
48
+
49
+
50
+ def test_finalize_exhausts_on_persistent_exception(core_with_mock_sdk, facts_file, no_sleep):
51
+ core_with_mock_sdk.sdk.fullscans.finalize_tier1.side_effect = Exception("down")
52
+
53
+ # Never raises; returns False after exhausting attempts.
54
+ assert core_with_mock_sdk.finalize_tier1_scan("full-1", facts_file) is False
55
+ assert core_with_mock_sdk.sdk.fullscans.finalize_tier1.call_count == TIER1_FINALIZE_MAX_ATTEMPTS
56
+
57
+
58
+ def test_finalize_exhausts_on_persistent_falsy(core_with_mock_sdk, facts_file, no_sleep):
59
+ core_with_mock_sdk.sdk.fullscans.finalize_tier1.return_value = False
60
+
61
+ assert core_with_mock_sdk.finalize_tier1_scan("full-1", facts_file) is False
62
+ assert core_with_mock_sdk.sdk.fullscans.finalize_tier1.call_count == TIER1_FINALIZE_MAX_ATTEMPTS
63
+
64
+
65
+ def test_finalize_returns_false_when_no_scan_id(core_with_mock_sdk, tmp_path):
66
+ path = tmp_path / ".socket.facts.json"
67
+ path.write_text(json.dumps({"components": []}), encoding="utf-8")
68
+
69
+ assert core_with_mock_sdk.finalize_tier1_scan("full-1", str(path)) is False
70
+ core_with_mock_sdk.sdk.fullscans.finalize_tier1.assert_not_called()
@@ -1270,7 +1270,7 @@ wheels = [
1270
1270
 
1271
1271
  [[package]]
1272
1272
  name = "socketsecurity"
1273
- version = "2.4.0"
1273
+ version = "2.4.2"
1274
1274
  source = { editable = "." }
1275
1275
  dependencies = [
1276
1276
  { name = "brotli", marker = "platform_python_implementation == 'CPython'" },
File without changes
File without changes
File without changes