socketsecurity 2.9.7__tar.gz → 2.10.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/CHANGELOG.md +27 -0
  2. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/PKG-INFO +1 -1
  3. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/docs/cli-reference.md +10 -8
  4. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/pyproject.toml +1 -1
  5. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/__init__.py +1 -1
  6. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/config.py +37 -16
  7. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/__init__.py +23 -2
  8. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/git_interface.py +70 -35
  9. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/output.py +18 -7
  10. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/socketcli.py +31 -22
  11. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/core/test_diff_generation.py +46 -1
  12. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_cli_config.py +3 -2
  13. socketsecurity-2.10.0/tests/unit/test_commit_message_truncation.py +113 -0
  14. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_full_scan_retry.py +43 -0
  15. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_git_interface.py +126 -0
  16. socketsecurity-2.10.0/tests/unit/test_summary_text.py +74 -0
  17. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/uv.lock +1 -1
  18. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/CODEOWNERS +0 -0
  19. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
  20. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
  21. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
  22. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  23. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/actions/setup-docker/action.yml +0 -0
  24. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/actions/setup-hatch/action.yml +0 -0
  25. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/actions/setup-sfw/action.yml +0 -0
  26. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/dependabot.yml +0 -0
  27. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/workflows/dependency-audit.yml +0 -0
  28. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/workflows/dependency-review.yml +0 -0
  29. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/workflows/docker-stable.yml +0 -0
  30. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/workflows/e2e-test.yml +0 -0
  31. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/workflows/package-check.yml +0 -0
  32. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/workflows/pr-preview.yml +0 -0
  33. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/workflows/python-tests.yml +0 -0
  34. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/workflows/release.yml +0 -0
  35. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/workflows/version-check.yml +0 -0
  36. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.github/zizmor.yml +0 -0
  37. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.gitignore +0 -0
  38. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.hooks/sync_version.py +0 -0
  39. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.pre-commit-config.yaml +0 -0
  40. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/.python-version +0 -0
  41. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/CONTRIBUTING.md +0 -0
  42. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/Dockerfile +0 -0
  43. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/Dockerfile.preview +0 -0
  44. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/LICENSE +0 -0
  45. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/Makefile +0 -0
  46. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/README.md +0 -0
  47. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/benchmarks/manifest_discovery.py +0 -0
  48. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/docs/ci-cd.md +0 -0
  49. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/docs/development.md +0 -0
  50. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/docs/troubleshooting.md +0 -0
  51. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/examples/config/sarif-dashboard-parity.json +0 -0
  52. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/examples/config/sarif-dashboard-parity.toml +0 -0
  53. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/examples/config/sarif-diff-ci-cd.json +0 -0
  54. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/examples/config/sarif-diff-ci-cd.toml +0 -0
  55. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/examples/config/sarif-instance-detail.json +0 -0
  56. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/examples/config/sarif-instance-detail.toml +0 -0
  57. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/instructions/gitlab-commit-status/uat.md +0 -0
  58. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/pytest.ini +0 -0
  59. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/scripts/build_container.sh +0 -0
  60. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/scripts/build_container_flexible.sh +0 -0
  61. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/scripts/deploy-test-docker.sh +0 -0
  62. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/scripts/deploy-test-pypi.sh +0 -0
  63. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/scripts/docker-entrypoint.sh +0 -0
  64. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/scripts/run.sh +0 -0
  65. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/session.md +0 -0
  66. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socket.yml +0 -0
  67. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/alert_selection.py +0 -0
  68. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/classes.py +0 -0
  69. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/cli_client.py +0 -0
  70. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/cli_run.py +0 -0
  71. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/exceptions.py +0 -0
  72. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/git_remote.py +0 -0
  73. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/helper/__init__.py +0 -0
  74. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/helper/socket_facts_loader.py +0 -0
  75. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/lazy_file_loader.py +0 -0
  76. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/log_uploader.py +0 -0
  77. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/logging.py +0 -0
  78. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/messages.py +0 -0
  79. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/pull_request.py +0 -0
  80. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/resource_utils.py +0 -0
  81. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/scm/__init__.py +0 -0
  82. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/scm/base.py +0 -0
  83. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/scm/client.py +0 -0
  84. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/scm/github.py +0 -0
  85. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/scm/gitlab.py +0 -0
  86. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/scm_comments.py +0 -0
  87. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/socket_config.py +0 -0
  88. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/streaming.py +0 -0
  89. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/tools/reachability.py +0 -0
  90. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/core/utils.py +0 -0
  91. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/fossa_compat.py +0 -0
  92. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/plugins/__init__.py +0 -0
  93. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/plugins/base.py +0 -0
  94. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/plugins/formatters/__init__.py +0 -0
  95. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/plugins/formatters/slack.py +0 -0
  96. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/plugins/jira.py +0 -0
  97. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/plugins/manager.py +0 -0
  98. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/plugins/slack.py +0 -0
  99. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/plugins/teams.py +0 -0
  100. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/socketsecurity/plugins/webhook.py +0 -0
  101. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/__init__.py +0 -0
  102. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/core/conftest.py +0 -0
  103. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/core/create_diff_input.json +0 -0
  104. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/core/test_diff_alerts.py +0 -0
  105. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/core/test_diff_scan_polling.py +0 -0
  106. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/core/test_facts_compression.py +0 -0
  107. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/core/test_full_scan_outputs.py +0 -0
  108. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/core/test_has_manifest_files.py +0 -0
  109. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/core/test_invalid_facts_marker.py +0 -0
  110. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/core/test_package_and_alerts.py +0 -0
  111. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/core/test_sdk_methods.py +0 -0
  112. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/core/test_supporting_methods.py +0 -0
  113. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/data/fullscans/create_response.json +0 -0
  114. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/data/fullscans/diff/stream_diff.json +0 -0
  115. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
  116. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/data/fullscans/head_scan/metadata.json +0 -0
  117. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
  118. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
  119. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/data/fullscans/new_scan/metadata.json +0 -0
  120. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
  121. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/data/repos/repo_info_error.json +0 -0
  122. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/data/repos/repo_info_no_head.json +0 -0
  123. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/data/repos/repo_info_success.json +0 -0
  124. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/data/settings/security-policy.json +0 -0
  125. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/e2e/fixtures/simple-npm/index.js +0 -0
  126. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/e2e/fixtures/simple-npm/package.json +0 -0
  127. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/e2e/fixtures/simple-pypi/requirements.txt +0 -0
  128. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/e2e/reach-facts-probe.sh +0 -0
  129. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/e2e/validate-gitlab.sh +0 -0
  130. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/e2e/validate-json.sh +0 -0
  131. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/e2e/validate-reachability.sh +0 -0
  132. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/e2e/validate-sarif.sh +0 -0
  133. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/e2e/validate-scan.sh +0 -0
  134. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/fixtures/fossa/README.md +0 -0
  135. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/fixtures/fossa/fossa-analyze-empty.json +0 -0
  136. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/fixtures/fossa/fossa-analyze-populated.json +0 -0
  137. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/fixtures/fossa/fossa-sbom-empty-deep.json +0 -0
  138. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/fixtures/fossa/fossa-sbom-populated.json +0 -0
  139. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/__init__.py +0 -0
  140. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_alert_selection.py +0 -0
  141. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_cli_run.py +0 -0
  142. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_client.py +0 -0
  143. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_config.py +0 -0
  144. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_dependency_overview.py +0 -0
  145. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_disable_ignore.py +0 -0
  146. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_exclude_paths.py +0 -0
  147. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_fossa_compat.py +0 -0
  148. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_fossa_parity.py +0 -0
  149. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_git_remote.py +0 -0
  150. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_github_buildkite_config.py +0 -0
  151. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_gitlab_auth.py +0 -0
  152. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_gitlab_auth_fallback.py +0 -0
  153. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_gitlab_commit_status.py +0 -0
  154. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_gitlab_format.py +0 -0
  155. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_ignore_authorization.py +0 -0
  156. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_ignore_telemetry_filtering.py +0 -0
  157. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_include_dirs.py +0 -0
  158. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_log_uploader.py +0 -0
  159. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_manifest_discovery.py +0 -0
  160. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_messages.py +0 -0
  161. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_output.py +0 -0
  162. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_pr_comment_rendering.py +0 -0
  163. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_pull_request_context.py +0 -0
  164. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_reachability.py +0 -0
  165. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_scan_scope.py +0 -0
  166. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_slack_plugin.py +0 -0
  167. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_slack_severity_normalization.py +0 -0
  168. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_socketcli.py +0 -0
  169. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_streaming.py +0 -0
  170. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/tests/unit/test_tier1_finalize.py +0 -0
  171. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/workflows/bitbucket-pipelines.yml +0 -0
  172. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/workflows/buildkite.yml +0 -0
  173. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/workflows/github-actions.yml +0 -0
  174. {socketsecurity-2.9.7 → socketsecurity-2.10.0}/workflows/gitlab-ci.yml +0 -0
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.10.0
4
+
5
+ ### Fixed
6
+
7
+ - `--base-commit-sha` now sets the range changed-file detection reads, so a manifest
8
+ changed anywhere between that commit and HEAD triggers a comparison. Previously only
9
+ a recognized GitHub, GitLab, Bitbucket, or Buildkite pull request read a full range,
10
+ and every other run saw the current commit alone.
11
+ - A base commit that cannot be resolved in the checkout now warns instead of narrowing
12
+ the comparison to a single commit.
13
+ - A full scan reports its findings as repository findings rather than new ones, and
14
+ labels its link `Report Url`. The summary states why the counts do not affect the
15
+ exit code.
16
+ - Removed and replaced packages with a namespace no longer render a duplicated purl in
17
+ the dependency overview comment.
18
+
19
+ - The 200-character cap on the commit message now applies to the value read from the
20
+ repository, not only to `--commit-message`. A truncated message ends in `...` and the
21
+ truncation is reported at INFO.
22
+ - A full scan refused for its size (HTTP 413, 414 or 431) now distinguishes possible
23
+ upload-size and request-metadata causes and reports what to shorten.
24
+
25
+ ### Changed
26
+
27
+ - `--ignore-commit-files` is documented as forcing a comparison, which is what it does.
28
+ The CLI reference and `--help` described it as forcing a full scan.
29
+
3
30
  ## 2.9.7
4
31
 
5
32
  ### Changed: bump pinned @coana-tech/cli to 15.10.51
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: socketsecurity
3
- Version: 2.9.7
3
+ Version: 2.10.0
4
4
  Summary: Socket Security CLI for CI/CD
5
5
  Project-URL: Homepage, https://socket.dev
6
6
  Author-email: Douglas Coburn <douglas@socket.dev>
@@ -256,7 +256,7 @@ If you don't want to provide the Socket API Token every time then you can use th
256
256
  | `--commit-message` | False | *auto* | Commit message (auto-detected from git) |
257
257
  | `--commit-sha` | False | *auto* | Commit SHA (auto-detected from git) |
258
258
  | `--base-scan-id` | False | | Full scan ID to diff against, overriding the repository's head scan as the baseline. Mutually exclusive with `--base-commit-sha` |
259
- | `--base-commit-sha`| False | | Commit SHA to prefer as the diff baseline, overriding the repository's head scan. The CLI uses its most recent matching full scan or the nearest scanned first-parent ancestor within 100 local commits. It errors (exit code 3, or `--exit-code-on-api-error`) if no scanned ancestor is reachable. Mutually exclusive with `--base-scan-id` |
259
+ | `--base-commit-sha`| False | | Commit SHA to prefer as the diff baseline, overriding the repository's head scan. The CLI uses its most recent matching full scan or the nearest scanned first-parent ancestor within 100 local commits. It errors (exit code 3, or `--exit-code-on-api-error`) if no scanned ancestor is reachable. Also sets the range changed-file detection reads, so a manifest changed anywhere between this commit and HEAD is seen. Mutually exclusive with `--base-scan-id` |
260
260
 
261
261
  > **Diffing against the merge base** — by default, PR scans are diffed against the repository's latest matching head scan, which may include newer default-branch commits than your PR branched from. To prefer the commit your PR is based on, compute the merge base and pass it as the baseline:
262
262
  >
@@ -267,6 +267,8 @@ If you don't want to provide the Socket API Token every time then you can use th
267
267
  >
268
268
  > `--base-commit-sha` does not create a scan of that commit. The CLI first looks for the newest non-temporary scan matching the repository, workspace, scan type, and exact commit. If the exact commit was not scanned, it walks up to 100 first-parent commits from that SHA in the local checkout and uses the nearest matching scanned ancestor. It logs a warning with the selected commit and distance because this produces a wider diff than the merge base.
269
269
  >
270
+ > Supplying `--base-commit-sha` also widens the range the CLI reads when deciding whether any manifest changed. Without it, and outside a recognized CI pull request or merge request, the CLI sees only the current commit, so a pull request whose manifest changed in an earlier commit is treated as a source-only change and the comparison is skipped. If the base commit cannot be resolved in the local checkout, the CLI warns and falls back to the current commit alone; deepen the clone or fetch the base commit to compare the full range.
271
+ >
270
272
  > Run `socketcli` regularly on the default branch so recent ancestors have scans. PR checkouts must also retain the merge base and enough first-parent history; shallow clones can shorten the search. Gaps are expected when CI cancels intermediate builds, commits use `[skip ci]`, pipelines are path-filtered, or the merge base predates your Socket rollout.
271
273
  >
272
274
  > If no scanned ancestor is reachable within the local 100-commit walk, the CLI **fails** (exit code 3, or your `--exit-code-on-api-error` value; exit 0 with `--disable-blocking`) instead of silently falling back to the repository head. API or permission failures also fail rather than being treated as a missing exact scan.
@@ -427,7 +429,7 @@ The launcher can be tuned via the `SOCKET_CLI_COANA_LAUNCHER` environment variab
427
429
  #### Advanced Configuration
428
430
  | Parameter | Required | Default | Description |
429
431
  |:-------------------------|:---------|:--------|:----------------------------------------------------------------------|
430
- | `--ignore-commit-files` | False | False | Ignore commit files |
432
+ | `--ignore-commit-files` | False | False | Compare regardless of which files changed, scanning every manifest |
431
433
  | `--disable-blocking` | False | False | Non-blocking CI mode: the CLI always exits **0**, even when blocking alerts are present (including with `--strict-blocking`). Also exits 0 on uncaught runtime errors and Socket API failures, so the job is treated as successful while findings and errors are still logged. Takes precedence over `--strict-blocking`. |
432
434
  | `--disable-ignore` | False | False | Disable support for `@SocketSecurity ignore` commands in PR comments. When set, alerts cannot be suppressed via comments and ignore instructions are hidden from comment output. See [Who can ignore an alert](#who-can-ignore-an-alert). |
433
435
  | `--ignore-authorization` | False | enforce | Who may suppress alerts with `@SocketSecurity ignore`. `enforce` requires write access and honors the command with a warning when the provider cannot report it; `strict` rejects it in that case; `off` honors any commenter. See [Who can ignore an alert](#who-can-ignore-an-alert). |
@@ -674,10 +676,10 @@ The CLI now automatically detects repository information from your git environme
674
676
  - **Commit message**: Latest commit message
675
677
  - **Committer information**: Git commit author details
676
678
  - **Default branch status**: Determined from git repository and CI environment
677
- - **Changed files**: Files modified in the current commit (for differential scanning)
679
+ - **Changed files**: Files modified in the current commit, or across the whole `--base-commit-sha`..HEAD range when a base commit is supplied (for differential scanning)
678
680
  > **Note on merge commits**:
679
681
  > Standard merges (two parents) are supported.
680
- > For *octopus merges* (three or more parents), Git only reports changes relative to the first parent. This can lead to incomplete or empty file lists if changes only exist relative to other parents. In these cases, differential scanning may be skipped. To ensure coverage, use `--ignore-commit-files` to force a full scan or specify files explicitly with `--files`.
682
+ > For *octopus merges* (three or more parents), Git only reports changes relative to the first parent. This can lead to incomplete or empty file lists if changes only exist relative to other parents. In these cases, differential scanning may be skipped. To ensure coverage, use `--ignore-commit-files` to compare regardless of the detected changes, or specify files explicitly with `--files`.
681
683
  ### Default Branch Detection
682
684
 
683
685
  The CLI uses intelligent default branch detection with the following priority:
@@ -728,11 +730,11 @@ GitLab token/auth behavior and CI examples are documented in [`ci-cd.md`](ci-cd.
728
730
 
729
731
  The CLI determines which files to scan based on the following logic:
730
732
 
731
- 1. **Git Commit Files (Default)**: The CLI automatically checks files changed in the current git commit. If any of these files match supported manifest patterns (like package.json, requirements.txt, etc.), a scan is triggered.
733
+ 1. **Git Commit Files (Default)**: The CLI automatically checks which files changed. In a recognized CI pull request or merge request, and whenever `--base-commit-sha` is supplied, that is every file changed across the range; otherwise it is the current commit alone. If any of them match supported manifest patterns (like package.json, requirements.txt, etc.), a comparison is run.
732
734
 
733
735
  2. **`--files` Parameter Override**: When specified, this parameter takes precedence over git commit detection. It accepts a JSON array of file paths to check for manifest files.
734
736
 
735
- 3. **`--ignore-commit-files` Flag**: When set, git commit files are ignored completely, and the CLI will scan all manifest files in the target directory regardless of what changed.
737
+ 3. **`--ignore-commit-files` Flag**: When set, the changed-file check is skipped entirely. The CLI runs the comparison regardless of what changed, and scans every manifest file in the target directory.
736
738
 
737
739
  4. **Automatic Fallback**: If no manifest files are found in git commit changes and no `--files` are specified, the CLI automatically switches to "API mode" and performs a full repository scan.
738
740
 
@@ -742,7 +744,7 @@ The CLI determines which files to scan based on the following logic:
742
744
 
743
745
  - **Differential Mode**: When manifest files are detected in changes, performs a diff scan with PR/MR comment integration
744
746
  - **API Mode**: When no manifest files are in changes, creates a full scan report without PR comments but still scans the entire repository
745
- - **Force Mode**: With `--ignore-commit-files`, always performs a full scan regardless of changes
747
+ - **Force Mode**: With `--ignore-commit-files`, always runs a comparison regardless of which files changed, over every manifest in the target path
746
748
  - **Forced Diff Mode**: With `--enable-diff`, forces differential mode even when using `--integration api` (without SCM integration)
747
749
 
748
750
  ### Examples
@@ -750,7 +752,7 @@ The CLI determines which files to scan based on the following logic:
750
752
  - **Commit with manifest file**: If your commit includes changes to `package.json`, a differential scan will be triggered automatically with PR comment integration.
751
753
  - **Commit without manifest files**: If your commit only changes non-manifest files (like `.github/workflows/socket.yaml`), the CLI automatically switches to API mode and performs a full repository scan.
752
754
  - **Using `--files`**: If you specify `--files '["package.json"]'`, the CLI will check if this file exists and is a manifest file before determining scan type.
753
- - **Using `--ignore-commit-files`**: This forces a full scan of all manifest files in the target path, regardless of what's in your commit.
755
+ - **Using `--ignore-commit-files`**: This runs the comparison regardless of what's in your commit, over all manifest files in the target path. Use it when the changed-file check would otherwise skip a comparison you need.
754
756
  - **Using `--enable-diff`**: Forces diff mode without SCM integration - useful when you want differential scanning but are using `--integration api`. For example: `socketcli --integration api --enable-diff --target-path /path/to/repo`
755
757
  - **Auto-detection**: Most CI/CD scenarios now work with just `socketcli --target-path /path/to/repo --scm github --pr-number $PR_NUM`
756
758
 
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "socketsecurity"
9
- version = "2.9.7"
9
+ version = "2.10.0"
10
10
  requires-python = ">= 3.11"
11
11
  license = {"file" = "LICENSE"}
12
12
  dependencies = [
@@ -1,3 +1,3 @@
1
1
  __author__ = 'socket.dev'
2
- __version__ = '2.9.7'
2
+ __version__ = '2.10.0'
3
3
  USER_AGENT = f'SocketPythonCLI/{__version__}'
@@ -19,6 +19,32 @@ def get_plugin_config_from_env(prefix: str) -> dict:
19
19
  return {}
20
20
 
21
21
 
22
+ # commit_message rides in the query string of POST /v0/orgs/{org}/full-scans, so an
23
+ # oversized message overflows the edge proxy's request line limit before the API ever
24
+ # sees it. The API itself has no length validation on the field; the rejection comes
25
+ # from the proxy, which reports 413, 414 or 431 depending on which limit it checks. 200
26
+ # chars is a conservative ceiling given URL encoding can 2-3x the raw character count.
27
+ MAX_COMMIT_MESSAGE_LENGTH = 200
28
+
29
+
30
+ COMMIT_MESSAGE_TRUNCATION_MARKER = "..."
31
+
32
+
33
+ def truncate_commit_message(commit_message: Optional[str]) -> Optional[str]:
34
+ """Cap commit_message to a length the full-scan request line can carry."""
35
+ if commit_message and len(commit_message) > MAX_COMMIT_MESSAGE_LENGTH:
36
+ # INFO, not DEBUG: the scan keeps the truncated value, so for a CI job that does
37
+ # not pass --enable-debug this line is the only explanation of why the message in
38
+ # the dashboard is clipped.
39
+ logging.info(
40
+ f"commit_message truncated from {len(commit_message)} to "
41
+ f"{MAX_COMMIT_MESSAGE_LENGTH} characters to stay within API request size limits"
42
+ )
43
+ keep = MAX_COMMIT_MESSAGE_LENGTH - len(COMMIT_MESSAGE_TRUNCATION_MARKER)
44
+ return commit_message[:keep] + COMMIT_MESSAGE_TRUNCATION_MARKER
45
+ return commit_message
46
+
47
+
22
48
  def load_cli_config_file(config_path: str) -> dict:
23
49
  """
24
50
  Load CLI defaults from a JSON or TOML file.
@@ -201,7 +227,13 @@ class CliConfig:
201
227
  legal: bool = False
202
228
  legal_format: str = "socket"
203
229
  config_file: Optional[str] = None
204
-
230
+
231
+ def __post_init__(self):
232
+ # Capped on construction so that every source of commit_message -- the
233
+ # --commit-message flag, a config file, the git backfill in socketcli -- lands
234
+ # under the limit.
235
+ self.commit_message = truncate_commit_message(self.commit_message)
236
+
205
237
  @classmethod
206
238
  def from_args(cls, args_list: Optional[List[str]] = None) -> 'CliConfig':
207
239
  parser = create_argument_parser()
@@ -257,19 +289,6 @@ class CliConfig:
257
289
  if commit_message and commit_message.startswith('"') and commit_message.endswith('"'):
258
290
  commit_message = commit_message[1:-1]
259
291
 
260
- # Truncate to avoid 413s from oversized URL query parameters.
261
- # The API has no application-layer length validation on commit_message;
262
- # the 413 originates from an infrastructure-layer URL length limit
263
- # (nginx/Cloudflare). 200 chars chosen as a conservative ceiling given
264
- # URL encoding can 2-3x raw character count.
265
- MAX_COMMIT_MESSAGE_LENGTH = 200
266
- if commit_message and len(commit_message) > MAX_COMMIT_MESSAGE_LENGTH:
267
- logging.debug(
268
- f"commit_message truncated from {len(commit_message)} to "
269
- f"{MAX_COMMIT_MESSAGE_LENGTH} characters to avoid API request size limits"
270
- )
271
- commit_message = commit_message[:MAX_COMMIT_MESSAGE_LENGTH]
272
-
273
292
  config_args = {
274
293
  'api_token': api_token,
275
294
  'repo': args.repo,
@@ -612,7 +631,9 @@ def create_argument_parser() -> argparse.ArgumentParser:
612
631
  help="Commit SHA to diff the new scan against, overriding the repository's head "
613
632
  "scan as the baseline. The CLI uses the most recent matching full scan, or "
614
633
  "the nearest scanned first-parent ancestor within 100 local commits when "
615
- "the commit itself was not scanned. Mutually exclusive with --base-scan-id."
634
+ "the commit itself was not scanned. Also sets the range changed-file "
635
+ "detection reads, so a manifest changed anywhere between this commit and "
636
+ "HEAD is seen. Mutually exclusive with --base-scan-id."
616
637
  )
617
638
 
618
639
  # Path and File options
@@ -913,7 +934,7 @@ def create_argument_parser() -> argparse.ArgumentParser:
913
934
  "--ignore-commit-files",
914
935
  dest="ignore_commit_files",
915
936
  action="store_true",
916
- help="Ignore commit files"
937
+ help="Compare against the baseline regardless of which files changed, scanning every manifest in the target path"
917
938
  )
918
939
  advanced_group.add_argument(
919
940
  "--ignore_commit_files",
@@ -113,6 +113,12 @@ FULL_SCAN_UPLOAD_BACKOFF_SCHEDULE_SECONDS = (10.0, 30.0, None)
113
113
  FULL_SCAN_UPLOAD_MAX_ATTEMPTS = len(FULL_SCAN_UPLOAD_BACKOFF_SCHEDULE_SECONDS)
114
114
  FULL_SCAN_UPLOAD_BACKOFF_JITTER_SECONDS = 2.0
115
115
 
116
+ # Statuses that mean the request is too large to process. Scan metadata travels in the
117
+ # query string of the full-scan POST, while manifests travel in its multipart body. A
118
+ # 413 can refer to either part; 414 points to the URL, and some proxies report 431 when
119
+ # the encoded request target exceeds their header limit. None are transient.
120
+ REQUEST_TOO_LARGE_STATUS_CODES = (413, 414, 431)
121
+
116
122
  # Diff-scan polling policy. The legacy scan comparison (fullscans.stream_diff) holds a
117
123
  # single HTTP connection open, fully idle, while the backend computes the diff; network
118
124
  # middleboxes with TCP idle timeouts (notably Azure NAT gateways, which default to
@@ -1118,6 +1124,23 @@ class Core:
1118
1124
  res = self.sdk.fullscans.post(upload_files, params, use_types=True, use_lazy_loading=True, max_open_files=50, base_paths=base_paths)
1119
1125
  break
1120
1126
  except APIFailure as error:
1127
+ if error.status_code in REQUEST_TOO_LARGE_STATUS_CODES:
1128
+ if error.status_code == 413:
1129
+ guidance = (
1130
+ "The response does not distinguish between an oversized multipart "
1131
+ "upload and oversized scan metadata in the request URL. Reduce the "
1132
+ "uploaded scan inputs, or pass a shorter --commit-message."
1133
+ )
1134
+ else:
1135
+ guidance = (
1136
+ "Scan metadata is sent in the request URL. Pass a shorter "
1137
+ "--commit-message or shorten other scan metadata."
1138
+ )
1139
+ raise APIFailure(
1140
+ f"Full scan request rejected as too large (HTTP {error.status_code}). "
1141
+ f"{guidance}\n{error}",
1142
+ status_code=error.status_code,
1143
+ ) from error
1121
1144
  if backoff_seconds is None or not error.is_transient_error():
1122
1145
  raise
1123
1146
  wait_seconds = backoff_seconds + random.uniform(
@@ -2267,8 +2290,6 @@ class Core:
2267
2290
  try:
2268
2291
  pkg = Package.from_diff_artifact(asdict(artifact))
2269
2292
  pkg = Core.update_package_values(pkg)
2270
- if pkg.namespace:
2271
- pkg.purl += f"{pkg.namespace}/{pkg.purl}"
2272
2293
  removed_packages[artifact.id] = pkg
2273
2294
  except KeyError:
2274
2295
  log.error(f"KeyError: Could not create package from removed artifact {artifact.id}")
@@ -11,10 +11,21 @@ from socketsecurity.core import log
11
11
  class Git:
12
12
  repo: Repo
13
13
  path: str
14
+ base_commit_sha: str | None
14
15
 
15
- def __init__(self, path: str):
16
+ def __init__(self, path: str, base_commit_sha: str | None = None):
17
+ """
18
+ Reads the repository state a scan is built from.
19
+
20
+ Args:
21
+ path: Path to the repository working tree
22
+ base_commit_sha: Commit the comparison should start from. Supplied when
23
+ the caller knows the range and no CI environment describes it, which
24
+ is the only way changed-file detection can see commits behind HEAD.
25
+ """
16
26
  initialization_start = time.perf_counter()
17
27
  self.path = path
28
+ self.base_commit_sha = base_commit_sha
18
29
  self._fetched_ref_commits = {}
19
30
  self.ensure_safe_directory(path)
20
31
  self.repo = Repo(path)
@@ -164,40 +175,62 @@ class Git:
164
175
  buildkite_pr = os.getenv('BUILDKITE_PULL_REQUEST')
165
176
  buildkite_base_ref = os.getenv('BUILDKITE_PULL_REQUEST_BASE_BRANCH')
166
177
  buildkite_head_ref = os.getenv('BUILDKITE_BRANCH')
167
- if self._is_buildkite_pull_request(buildkite_pr) and buildkite_base_ref:
168
- detected = self._detect_pull_request_changes(
169
- provider="Buildkite",
170
- base_ref=buildkite_base_ref,
171
- head_ref=buildkite_head_ref,
172
- )
173
- if detected:
174
- detection_source = "buildkite-pr"
175
- elif github_event_name == 'pull_request' and github_base_ref:
178
+
179
+ # An explicitly supplied base commit states the comparison range outright,
180
+ # so it is honored before any inference from CI environment variables.
181
+ if self.base_commit_sha:
176
182
  detected = self._detect_pull_request_changes(
177
- provider="GitHub",
178
- base_ref=github_base_ref,
179
- head_ref=github_head_ref,
183
+ provider="explicit base commit",
184
+ base_ref=self.base_commit_sha,
185
+ head_ref=None,
186
+ use_merge_base=False,
180
187
  )
181
188
  if detected:
182
- detection_source = "github-pr"
183
- # Commits to default branch (push events)
184
- elif github_event_name == 'push' and github_before_sha and github_sha:
185
- try:
186
- diff_files = self.repo.git.diff('--name-only', f'{github_before_sha}..{github_sha}')
187
- self.show_files = diff_files.splitlines()
188
- log.debug(f"Changed files detected via git diff (GitHub push): {self.show_files}")
189
- detected = True
190
- detection_source = "github-push"
191
- except Exception as error:
192
- log.debug(f"Failed to get changed files via git diff (GitHub push): {error}")
193
- elif github_event_name == 'push':
194
- try:
195
- self.show_files = self.repo.git.show(self.commit, name_only=True, format="%n").splitlines()
196
- log.debug(f"Changed files detected via git show (GitHub push fallback): {self.show_files}")
197
- detected = True
198
- detection_source = "github-push-fallback"
199
- except Exception as error:
200
- log.debug(f"Failed to get changed files via git show (GitHub push fallback): {error}")
189
+ detection_source = "explicit-base-commit"
190
+ else:
191
+ log.warning(
192
+ f"Could not resolve base commit {self.base_commit_sha} in this "
193
+ "checkout, so changed-file detection falls back to the current "
194
+ "commit alone. A manifest changed earlier in the range will not "
195
+ "be seen, which can skip the comparison entirely. Deepen the "
196
+ "clone or fetch the base commit to compare the full range."
197
+ )
198
+
199
+ if not detected:
200
+ if self._is_buildkite_pull_request(buildkite_pr) and buildkite_base_ref:
201
+ detected = self._detect_pull_request_changes(
202
+ provider="Buildkite",
203
+ base_ref=buildkite_base_ref,
204
+ head_ref=buildkite_head_ref,
205
+ )
206
+ if detected:
207
+ detection_source = "buildkite-pr"
208
+ elif github_event_name == 'pull_request' and github_base_ref:
209
+ detected = self._detect_pull_request_changes(
210
+ provider="GitHub",
211
+ base_ref=github_base_ref,
212
+ head_ref=github_head_ref,
213
+ )
214
+ if detected:
215
+ detection_source = "github-pr"
216
+ # Commits to default branch (push events)
217
+ elif github_event_name == 'push' and github_before_sha and github_sha:
218
+ try:
219
+ diff_files = self.repo.git.diff('--name-only', f'{github_before_sha}..{github_sha}')
220
+ self.show_files = diff_files.splitlines()
221
+ log.debug(f"Changed files detected via git diff (GitHub push): {self.show_files}")
222
+ detected = True
223
+ detection_source = "github-push"
224
+ except Exception as error:
225
+ log.debug(f"Failed to get changed files via git diff (GitHub push): {error}")
226
+ elif github_event_name == 'push':
227
+ try:
228
+ self.show_files = self.repo.git.show(self.commit, name_only=True, format="%n").splitlines()
229
+ log.debug(f"Changed files detected via git show (GitHub push fallback): {self.show_files}")
230
+ detected = True
231
+ detection_source = "github-push-fallback"
232
+ except Exception as error:
233
+ log.debug(f"Failed to get changed files via git show (GitHub push fallback): {error}")
201
234
  # GitLab CI Merge Request context
202
235
  if not detected:
203
236
  gitlab_target = os.getenv('CI_MERGE_REQUEST_TARGET_BRANCH_NAME')
@@ -316,8 +349,9 @@ class Git:
316
349
  provider: str,
317
350
  base_ref: str,
318
351
  head_ref: str | None,
352
+ use_merge_base: bool = True,
319
353
  ) -> bool:
320
- """Detect a full PR range locally, fetching only refs needed to complete it."""
354
+ """Detect a base-to-head range locally, fetching only refs needed to complete it."""
321
355
  base_commit = self._resolve_ref(base_ref)
322
356
  if base_commit is None:
323
357
  base_commit = self._fetch_ref(base_ref, f"{provider} pull-request base ref missing")
@@ -326,7 +360,8 @@ class Git:
326
360
  return False
327
361
 
328
362
  head_commit = self.commit.hexsha
329
- diff_range = f"{base_commit}...{head_commit}"
363
+ range_separator = "..." if use_merge_base else ".."
364
+ diff_range = f"{base_commit}{range_separator}{head_commit}"
330
365
  try:
331
366
  diff_files = self.repo.git.diff("--name-only", diff_range)
332
367
  self.show_files = diff_files.splitlines()
@@ -352,7 +387,7 @@ class Git:
352
387
  try:
353
388
  diff_files = self.repo.git.diff(
354
389
  "--name-only",
355
- f"{base_commit}...{head_commit}",
390
+ f"{base_commit}{range_separator}{head_commit}",
356
391
  )
357
392
  self.show_files = diff_files.splitlines()
358
393
  log.debug(
@@ -229,10 +229,8 @@ class OutputHandler:
229
229
 
230
230
  def build_summary_text(self, diff_report: Diff) -> str:
231
231
  """Render the console summary text for stdout and file output."""
232
- if (
233
- getattr(diff_report, "is_full_scan", False)
234
- and not getattr(diff_report, "alerts_fetched", False)
235
- ):
232
+ is_full_scan = getattr(diff_report, "is_full_scan", False)
233
+ if is_full_scan and not getattr(diff_report, "alerts_fetched", False):
236
234
  lines = ["Full scan completed. Findings were not fetched for console output."]
237
235
  report_link = getattr(diff_report, "report_url", "") or getattr(
238
236
  diff_report, "diff_url", ""
@@ -264,20 +262,33 @@ class OutputHandler:
264
262
  selected_diff = clone_diff_with_selected_alerts(diff_report, selected_alerts)
265
263
  console_security_comment = Messages.create_console_security_alert_table(selected_diff)
266
264
 
265
+ # A full scan has no baseline, so everything it carries is a finding in the
266
+ # repository rather than one the change introduced. Calling those NEW would
267
+ # also contradict the exit code, which stays 0 because no finding can be
268
+ # attributed to the change.
269
+ blocking_label = "Blocking issues" if is_full_scan else "NEW blocking issues"
270
+ warning_label = "Warning issues" if is_full_scan else "NEW warning issues"
271
+ link_label = "Report Url" if is_full_scan else "Diff Url"
272
+
267
273
  lines = ["Security issues detected by Socket Security:"]
268
274
  if new_blocking > 0:
269
- lines.append(f" - NEW blocking issues: {new_blocking}")
275
+ lines.append(f" - {blocking_label}: {new_blocking}")
270
276
  if new_warning > 0:
271
- lines.append(f" - NEW warning issues: {new_warning}")
277
+ lines.append(f" - {warning_label}: {new_warning}")
272
278
  if unchanged_blocking > 0:
273
279
  lines.append(
274
280
  f" - EXISTING blocking issues: {unchanged_blocking} (causing failure due to --strict-blocking)"
275
281
  )
276
282
  if unchanged_warning > 0:
277
283
  lines.append(f" - EXISTING warning issues: {unchanged_warning}")
284
+ if is_full_scan:
285
+ lines.append(
286
+ " Reported against the whole repository, with no baseline to compare "
287
+ "against, so these do not affect the exit code."
288
+ )
278
289
 
279
290
  report_link = getattr(diff_report, "report_url", "") or getattr(diff_report, "diff_url", "")
280
- lines.append(f"Diff Url: {report_link}")
291
+ lines.append(f"{link_label}: {report_link}")
281
292
  lines.append("")
282
293
  lines.append(str(console_security_comment))
283
294
  return "\n".join(lines)
@@ -12,7 +12,7 @@ from git import InvalidGitRepositoryError, NoSuchPathError
12
12
  from socketdev import socketdev
13
13
  from socketdev.fullscans import FullScanParams
14
14
 
15
- from socketsecurity.config import CliConfig
15
+ from socketsecurity.config import CliConfig, truncate_commit_message
16
16
  from socketsecurity.core import Core
17
17
  from socketsecurity.core.classes import Diff
18
18
  from socketsecurity.core.cli_client import CliClient
@@ -210,6 +210,35 @@ def create_scm_scan(
210
210
  return diff, False
211
211
 
212
212
 
213
+ def apply_git_context(config: CliConfig) -> Tuple[bool, Optional[Git]]:
214
+ """
215
+ Fill in any repo details the caller did not pass from the checkout at target_path.
216
+
217
+ Returns whether target_path is a git repository, along with the Git handle when it is.
218
+ """
219
+ try:
220
+ git_repo = Git(config.target_path, base_commit_sha=config.base_commit_sha)
221
+ except InvalidGitRepositoryError:
222
+ log.debug("Not a git repository, setting ignore_commit_files=True")
223
+ config.ignore_commit_files = True
224
+ return False, None
225
+ except NoSuchPathError:
226
+ raise Exception(f"Unable to find path {config.target_path}")
227
+
228
+ if not config.repo:
229
+ config.repo = git_repo.repo_name
230
+ if not config.commit_sha:
231
+ config.commit_sha = git_repo.commit_str
232
+ if not config.branch:
233
+ config.branch = git_repo.branch
234
+ if not config.committers:
235
+ config.committers = [git_repo.get_formatted_committer()]
236
+ if not config.commit_message:
237
+ # A repository's commit message is unbounded and ships in the query string.
238
+ config.commit_message = truncate_commit_message(git_repo.commit_message)
239
+ return True, git_repo
240
+
241
+
213
242
  def build_socket_sdk(config: CliConfig) -> socketdev:
214
243
  cli_user_agent_string = f"SocketPythonCLI/{config.version}"
215
244
  return socketdev(
@@ -402,27 +431,7 @@ def main_code():
402
431
  discovered_scan_files = None
403
432
 
404
433
  # Git setup
405
- is_repo = False
406
- git_repo: Git
407
- try:
408
- git_repo = Git(config.target_path)
409
- is_repo = True
410
- if not config.repo:
411
- config.repo = git_repo.repo_name
412
- if not config.commit_sha:
413
- config.commit_sha = git_repo.commit_str
414
- if not config.branch:
415
- config.branch = git_repo.branch
416
- if not config.committers:
417
- config.committers = [git_repo.get_formatted_committer()]
418
- if not config.commit_message:
419
- config.commit_message = git_repo.commit_message
420
- except InvalidGitRepositoryError:
421
- is_repo = False
422
- log.debug("Not a git repository, setting ignore_commit_files=True")
423
- config.ignore_commit_files = True
424
- except NoSuchPathError:
425
- raise Exception(f"Unable to find path {config.target_path}")
434
+ is_repo, git_repo = apply_git_context(config)
426
435
 
427
436
  # Track whether repo/branch fell back to the default sentinels so reachability can skip
428
437
  # forwarding them as coana cache-bucket keys (computed before any workspace suffixing).
@@ -3,7 +3,7 @@ from dataclasses import asdict, fields
3
3
  from pathlib import Path
4
4
 
5
5
  import pytest
6
- from socketdev.fullscans import DiffArtifact
6
+ from socketdev.fullscans import DiffArtifact, StreamDiffResponse
7
7
 
8
8
  from socketsecurity.core import Core
9
9
  from socketsecurity.core.classes import Package
@@ -312,3 +312,48 @@ def print_added_and_removed(added, removed):
312
312
  # pkg1_purl = next(p for p in diff.new_packages if p.id == "pkg1")
313
313
  # assert hasattr(pkg1_purl, "capabilities")
314
314
  # assert set(pkg1_purl.capabilities) == {"File System Access", "Network Access"}
315
+
316
+
317
+ def _namespaced_diff_response(namespace: str = "com.example"):
318
+ """One namespaced artifact, delivered as both an addition and a removal."""
319
+ raw = json.loads(
320
+ (Path(__file__).parent.parent / "data/fullscans/diff/stream_diff.json").read_text()
321
+ )
322
+ template = raw["data"]["artifacts"]["added"][0]
323
+ artifacts = {bucket: [] for bucket in ("added", "removed", "unchanged", "replaced", "updated")}
324
+ for bucket in ("added", "removed"):
325
+ artifacts[bucket].append(
326
+ dict(
327
+ template,
328
+ diffType=bucket,
329
+ head=None,
330
+ base=None,
331
+ id=f"namespaced-{bucket}",
332
+ namespace=namespace,
333
+ name="widget",
334
+ version="1.0.0",
335
+ type="maven",
336
+ )
337
+ )
338
+ return StreamDiffResponse.from_dict({
339
+ "success": raw["success"],
340
+ "status": raw["status"],
341
+ "data": {**raw["data"], "artifacts": artifacts},
342
+ })
343
+
344
+
345
+ def test_removed_package_purl_matches_the_added_form(core):
346
+ """A namespace belongs in the purl once, whichever bucket the artifact arrives in.
347
+
348
+ The purl reaches the dependency overview comment verbatim, so a second copy of
349
+ the namespace renders as an unreadable package name on every removed or
350
+ replaced row.
351
+ """
352
+ core.sdk.fullscans.stream_diff.side_effect = None
353
+ core.sdk.fullscans.stream_diff.return_value = _namespaced_diff_response()
354
+ core.sdk.diffscans.create_from_ids.side_effect = Exception("diff-scans unavailable")
355
+
356
+ added, removed, _ = core.get_added_and_removed_packages("head", "new")
357
+
358
+ assert added["namespaced-added"].purl == "com.example/widget@1.0.0"
359
+ assert removed["namespaced-removed"].purl == added["namespaced-added"].purl
@@ -31,14 +31,15 @@ class TestCommitMessageTruncation:
31
31
  config = CliConfig.from_args(
32
32
  ["--api-token", "test", "--commit-message", "a" * 250]
33
33
  )
34
- assert config.commit_message == "a" * 200
34
+ assert config.commit_message == "a" * 197 + "..."
35
+ assert len(config.commit_message) == 200
35
36
 
36
37
  def test_quote_strip_runs_before_truncation(self):
37
38
  quoted = '"' + ("b" * 250) + '"'
38
39
  config = CliConfig.from_args(
39
40
  ["--api-token", "test", "--commit-message", quoted]
40
41
  )
41
- assert config.commit_message == "b" * 200
42
+ assert config.commit_message == "b" * 197 + "..."
42
43
 
43
44
 
44
45
  class TestCliConfig: