socketsecurity 2.6.0__tar.gz → 2.6.3__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 (156) hide show
  1. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/actions/setup-docker/action.yml +3 -3
  2. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/actions/setup-sfw/action.yml +1 -1
  3. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/e2e-test.yml +1 -1
  4. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/package-check.yml +1 -1
  5. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/pr-preview.yml +1 -1
  6. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/python-tests.yml +2 -2
  7. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/release.yml +2 -2
  8. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/CHANGELOG.md +50 -0
  9. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/PKG-INFO +13 -13
  10. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/docs/cli-reference.md +1 -1
  11. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/pyproject.toml +13 -13
  12. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/__init__.py +1 -1
  13. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/__init__.py +190 -33
  14. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/tools/reachability.py +3 -3
  15. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/conftest.py +21 -0
  16. socketsecurity-2.6.3/tests/core/test_diff_scan_polling.py +122 -0
  17. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/test_sdk_methods.py +45 -13
  18. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/uv.lock +46 -46
  19. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/CODEOWNERS +0 -0
  20. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
  21. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
  22. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
  23. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  24. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/actions/setup-hatch/action.yml +0 -0
  25. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/dependabot.yml +0 -0
  26. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/dependency-review.yml +0 -0
  27. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/docker-stable.yml +0 -0
  28. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/version-check.yml +0 -0
  29. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/zizmor.yml +0 -0
  30. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.gitignore +0 -0
  31. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.hooks/sync_version.py +0 -0
  32. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.pre-commit-config.yaml +0 -0
  33. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.python-version +0 -0
  34. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/CONTRIBUTING.md +0 -0
  35. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/Dockerfile +0 -0
  36. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/Dockerfile.preview +0 -0
  37. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/LICENSE +0 -0
  38. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/Makefile +0 -0
  39. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/README.md +0 -0
  40. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/docs/ci-cd.md +0 -0
  41. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/docs/development.md +0 -0
  42. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/docs/troubleshooting.md +0 -0
  43. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/examples/config/sarif-dashboard-parity.json +0 -0
  44. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/examples/config/sarif-dashboard-parity.toml +0 -0
  45. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/examples/config/sarif-diff-ci-cd.json +0 -0
  46. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/examples/config/sarif-diff-ci-cd.toml +0 -0
  47. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/examples/config/sarif-instance-detail.json +0 -0
  48. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/examples/config/sarif-instance-detail.toml +0 -0
  49. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/instructions/gitlab-commit-status/uat.md +0 -0
  50. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/pytest.ini +0 -0
  51. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/scripts/build_container.sh +0 -0
  52. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/scripts/build_container_flexible.sh +0 -0
  53. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/scripts/deploy-test-docker.sh +0 -0
  54. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/scripts/deploy-test-pypi.sh +0 -0
  55. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/scripts/docker-entrypoint.sh +0 -0
  56. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/scripts/run.sh +0 -0
  57. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/session.md +0 -0
  58. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socket.yml +0 -0
  59. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/config.py +0 -0
  60. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/alert_selection.py +0 -0
  61. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/classes.py +0 -0
  62. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/cli_client.py +0 -0
  63. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/cli_run.py +0 -0
  64. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/exceptions.py +0 -0
  65. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/git_interface.py +0 -0
  66. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/helper/__init__.py +0 -0
  67. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/helper/socket_facts_loader.py +0 -0
  68. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/lazy_file_loader.py +0 -0
  69. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/log_uploader.py +0 -0
  70. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/logging.py +0 -0
  71. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/messages.py +0 -0
  72. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/resource_utils.py +0 -0
  73. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/scm/__init__.py +0 -0
  74. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/scm/base.py +0 -0
  75. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/scm/client.py +0 -0
  76. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/scm/github.py +0 -0
  77. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/scm/gitlab.py +0 -0
  78. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/scm_comments.py +0 -0
  79. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/socket_config.py +0 -0
  80. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/streaming.py +0 -0
  81. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/utils.py +0 -0
  82. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/fossa_compat.py +0 -0
  83. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/output.py +0 -0
  84. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/__init__.py +0 -0
  85. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/base.py +0 -0
  86. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/formatters/__init__.py +0 -0
  87. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/formatters/slack.py +0 -0
  88. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/jira.py +0 -0
  89. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/manager.py +0 -0
  90. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/slack.py +0 -0
  91. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/teams.py +0 -0
  92. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/webhook.py +0 -0
  93. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/socketcli.py +0 -0
  94. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/__init__.py +0 -0
  95. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/create_diff_input.json +0 -0
  96. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/test_diff_alerts.py +0 -0
  97. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/test_diff_generation.py +0 -0
  98. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/test_facts_compression.py +0 -0
  99. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/test_has_manifest_files.py +0 -0
  100. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/test_package_and_alerts.py +0 -0
  101. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/test_supporting_methods.py +0 -0
  102. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/create_response.json +0 -0
  103. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/diff/stream_diff.json +0 -0
  104. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
  105. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/head_scan/metadata.json +0 -0
  106. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
  107. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
  108. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/new_scan/metadata.json +0 -0
  109. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
  110. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/repos/repo_info_error.json +0 -0
  111. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/repos/repo_info_no_head.json +0 -0
  112. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/repos/repo_info_success.json +0 -0
  113. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/settings/security-policy.json +0 -0
  114. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/fixtures/simple-npm/index.js +0 -0
  115. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/fixtures/simple-npm/package.json +0 -0
  116. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/fixtures/simple-pypi/requirements.txt +0 -0
  117. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/reach-facts-probe.sh +0 -0
  118. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/validate-gitlab.sh +0 -0
  119. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/validate-json.sh +0 -0
  120. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/validate-reachability.sh +0 -0
  121. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/validate-sarif.sh +0 -0
  122. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/validate-scan.sh +0 -0
  123. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/fixtures/fossa/README.md +0 -0
  124. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/fixtures/fossa/fossa-analyze-empty.json +0 -0
  125. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/fixtures/fossa/fossa-analyze-populated.json +0 -0
  126. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/fixtures/fossa/fossa-sbom-empty-deep.json +0 -0
  127. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/fixtures/fossa/fossa-sbom-populated.json +0 -0
  128. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/__init__.py +0 -0
  129. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_alert_selection.py +0 -0
  130. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_cli_config.py +0 -0
  131. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_cli_run.py +0 -0
  132. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_client.py +0 -0
  133. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_config.py +0 -0
  134. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_dependency_overview.py +0 -0
  135. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_disable_ignore.py +0 -0
  136. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_exclude_paths.py +0 -0
  137. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_fossa_compat.py +0 -0
  138. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_fossa_parity.py +0 -0
  139. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_full_scan_retry.py +0 -0
  140. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_gitlab_auth.py +0 -0
  141. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_gitlab_auth_fallback.py +0 -0
  142. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_gitlab_commit_status.py +0 -0
  143. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_gitlab_format.py +0 -0
  144. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_ignore_telemetry_filtering.py +0 -0
  145. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_include_dirs.py +0 -0
  146. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_log_uploader.py +0 -0
  147. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_output.py +0 -0
  148. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_reachability.py +0 -0
  149. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_slack_plugin.py +0 -0
  150. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_socketcli.py +0 -0
  151. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_streaming.py +0 -0
  152. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_tier1_finalize.py +0 -0
  153. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/workflows/bitbucket-pipelines.yml +0 -0
  154. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/workflows/buildkite.yml +0 -0
  155. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/workflows/github-actions.yml +0 -0
  156. {socketsecurity-2.6.0 → socketsecurity-2.6.3}/workflows/gitlab-ci.yml +0 -0
@@ -19,10 +19,10 @@ inputs:
19
19
  runs:
20
20
  using: "composite"
21
21
  steps:
22
- - uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
22
+ - uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
23
23
  if: inputs.enable-qemu == 'true'
24
- - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
25
- - uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
24
+ - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
25
+ - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
26
26
  with:
27
27
  username: ${{ inputs.dockerhub-username }}
28
28
  password: ${{ inputs.dockerhub-token }}
@@ -27,7 +27,7 @@ runs:
27
27
  using: "composite"
28
28
  steps:
29
29
  - if: ${{ inputs.python == 'true' || inputs.uv == 'true' }}
30
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
30
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
31
31
  with:
32
32
  python-version: "3.12"
33
33
 
@@ -80,7 +80,7 @@ jobs:
80
80
  fetch-depth: 0
81
81
  persist-credentials: false
82
82
 
83
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
83
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
84
84
  with:
85
85
  python-version: '3.12'
86
86
 
@@ -23,7 +23,7 @@ jobs:
23
23
  fetch-depth: 1
24
24
  persist-credentials: false
25
25
 
26
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
26
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
27
27
  with:
28
28
  python-version: "3.12"
29
29
 
@@ -107,7 +107,7 @@ jobs:
107
107
  fetch-depth: 0
108
108
  persist-credentials: false
109
109
 
110
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
110
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
111
111
  with:
112
112
  python-version: "3.12"
113
113
 
@@ -40,7 +40,7 @@ jobs:
40
40
  fetch-depth: 1
41
41
  persist-credentials: false
42
42
  - name: 🐍 setup python
43
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
43
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
44
44
  with:
45
45
  python-version: ${{ env.PYTHON_VERSION }}
46
46
  - name: 🛠️ install deps
@@ -76,7 +76,7 @@ jobs:
76
76
  fetch-depth: 1
77
77
  persist-credentials: false
78
78
  - name: 🐍 setup python
79
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
79
+ uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
80
80
  with:
81
81
  python-version: "3.10"
82
82
  - name: 🚫 verify install is rejected on unsupported python
@@ -14,7 +14,7 @@ jobs:
14
14
  with:
15
15
  fetch-depth: 0
16
16
  persist-credentials: false
17
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
17
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
18
18
  with:
19
19
  python-version: '3.13'
20
20
 
@@ -66,7 +66,7 @@ jobs:
66
66
 
67
67
  - name: Publish to PyPI
68
68
  if: steps.version_check.outputs.pypi_exists != 'true'
69
- uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
69
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
70
70
 
71
71
  - name: Set up Docker publishing
72
72
  uses: ./.github/actions/setup-docker
@@ -1,5 +1,55 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.6.3
4
+
5
+ ### Fixed: SBOM fetch failures no longer produce empty reports
6
+
7
+ - `Core.get_sbom_data` now raises `APIFailure` when the full-scan stream fetch
8
+ fails, so the run exits through the CLI's API-error handling (exit code 3 by
9
+ default; `--disable-blocking` still exits 0) instead of writing empty
10
+ GitLab dependency-scanning, license, and SARIF reports.
11
+ - The underlying stream-parse failure was fixed in `socketdev` 3.4.2 (already
12
+ pinned to `3.5.0`): unrecognized purl types such as `generic` now resolve
13
+ instead of raising, and individual unparseable artifacts are skipped rather
14
+ than failing the whole response.
15
+
16
+ ### Changed: bump pinned @coana-tech/cli to 15.10.4
17
+
18
+ - Bumped the pinned reachability engine (`@coana-tech/cli`) from `15.10.3` to
19
+ `15.10.4`. See the [Coana changelogs](https://docs.coana.tech/changelogs) for
20
+ engine changes.
21
+
22
+ ### Changed: dependency updates and dev/test dependency pinning
23
+
24
+ - Bumped pinned runtime dependencies (`beautifulsoup4` `4.15.0`,
25
+ `markdown` `3.10.3`).
26
+ - Pinned the `test` and `dev` dependency groups to exact versions, extending
27
+ the exact-pinning policy introduced in 2.6.0 to all dependency groups.
28
+ - Updated GitHub Actions used in CI workflows. No effect on the installed
29
+ CLI.
30
+
31
+ ## 2.6.1
32
+
33
+ ### Changed: scan comparison now polls the diff-scans endpoints
34
+
35
+ - Diff mode no longer holds a single idle HTTP connection open while the API
36
+ computes the scan comparison. The CLI now creates a diff-scan resource
37
+ (`POST /orgs/{org}/diff-scans/from-ids`) and polls
38
+ `GET /orgs/{org}/diff-scans/{id}?cached=true` with short, bounded requests
39
+ until the comparison is ready (HTTP 200 instead of 202). This fixes
40
+ intermittent `Connection reset by peer` failures on the final comparison
41
+ step when scans take several minutes to compare and network middleboxes
42
+ (e.g. Azure NAT gateways, which default to a 4-minute TCP idle timeout)
43
+ reap the idle connection.
44
+ - Duplicate scan pairs are resolved after an HTTP 409 and then polled through
45
+ the same cached endpoint. This avoids automatically following the API's 302
46
+ duplicate redirect with an uncached, potentially long-lived GET request.
47
+ - The change is transparent: no flags or workflow changes are needed. If the
48
+ org API token is missing the `diff-scans:create`, `diff-scans:list` or
49
+ `full-scans:list` scopes — or the new flow fails for any other reason — the
50
+ CLI logs a warning and falls back to the legacy streaming comparison.
51
+ - Requires the pinned `socketdev==3.5.0` SDK.
52
+
3
53
  ## 2.6.0
4
54
 
5
55
  ### Changed: pin all Python dependencies
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: socketsecurity
3
- Version: 2.6.0
3
+ Version: 2.6.3
4
4
  Summary: Socket Security CLI for CI/CD
5
5
  Project-URL: Homepage, https://socket.dev
6
6
  Author-email: Douglas Coburn <douglas@socket.dev>
@@ -33,11 +33,11 @@ Classifier: Intended Audience :: Developers
33
33
  Classifier: Programming Language :: Python :: 3.11
34
34
  Classifier: Programming Language :: Python :: 3.12
35
35
  Requires-Python: >=3.11
36
- Requires-Dist: beautifulsoup4==4.14.3
36
+ Requires-Dist: beautifulsoup4==4.15.0
37
37
  Requires-Dist: brotli==1.2.0; platform_python_implementation == 'CPython'
38
38
  Requires-Dist: brotlicffi==1.2.0.1; platform_python_implementation != 'CPython'
39
39
  Requires-Dist: gitpython==3.1.57
40
- Requires-Dist: markdown==3.10.2
40
+ Requires-Dist: markdown==3.10.3
41
41
  Requires-Dist: mdutils==1.8.1
42
42
  Requires-Dist: packaging==26.2
43
43
  Requires-Dist: prettytable==3.18.0
@@ -45,17 +45,17 @@ Requires-Dist: python-dotenv==1.2.2
45
45
  Requires-Dist: requests==2.34.2
46
46
  Requires-Dist: socketdev==3.5.0
47
47
  Provides-Extra: dev
48
- Requires-Dist: hatch; extra == 'dev'
49
- Requires-Dist: pre-commit; extra == 'dev'
50
- Requires-Dist: ruff>=0.3.0; extra == 'dev'
51
- Requires-Dist: twine; extra == 'dev'
52
- Requires-Dist: uv>=0.1.0; extra == 'dev'
48
+ Requires-Dist: hatch==1.17.1; extra == 'dev'
49
+ Requires-Dist: pre-commit==4.6.1; extra == 'dev'
50
+ Requires-Dist: ruff==0.16.1; extra == 'dev'
51
+ Requires-Dist: twine==7.0.0; extra == 'dev'
52
+ Requires-Dist: uv==0.12.0; extra == 'dev'
53
53
  Provides-Extra: test
54
- Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
55
- Requires-Dist: pytest-cov>=4.1.0; extra == 'test'
56
- Requires-Dist: pytest-mock>=3.12.0; extra == 'test'
57
- Requires-Dist: pytest-watch>=4.2.0; extra == 'test'
58
- Requires-Dist: pytest>=7.4.0; extra == 'test'
54
+ Requires-Dist: pytest-asyncio==1.4.0; extra == 'test'
55
+ Requires-Dist: pytest-cov==7.1.0; extra == 'test'
56
+ Requires-Dist: pytest-mock==3.15.1; extra == 'test'
57
+ Requires-Dist: pytest-watch==4.2.0; extra == 'test'
58
+ Requires-Dist: pytest==9.1.1; extra == 'test'
59
59
  Description-Content-Type: text/markdown
60
60
 
61
61
  # Socket Security CLI
@@ -275,7 +275,7 @@ If you don't want to provide the Socket API Token every time then you can use th
275
275
  | Parameter | Required | Default | Description |
276
276
  |:---------------------------------|:---------|:--------|:---------------------------------------------------------------------------------------------------------------------------|
277
277
  | `--reach` | False | False | Enable reachability analysis to identify which vulnerable functions are actually called by your code. Creates a full application reachability scan (`scan_type=socket_tier1`). |
278
- | `--reach-version` | False | 15.10.3 | Version of @coana-tech/cli to use. Defaults to the pinned version that ships with this CLI release, so the engine only changes when you upgrade the Socket CLI. Pass `latest` to always use the newest published version (opt-in auto-update), or an explicit version (e.g. `1.2.3`) to pin it. |
278
+ | `--reach-version` | False | 15.10.4 | Version of @coana-tech/cli to use. Defaults to the pinned version that ships with this CLI release, so the engine only changes when you upgrade the Socket CLI. Pass `latest` to always use the newest published version (opt-in auto-update), or an explicit version (e.g. `1.2.3`) to pin it. |
279
279
  | `--reach-analysis-timeout` | False | 10m | Timeout for each reachability analysis run, e.g. `90s`, `10m` or `1h`. Omitted by default, so coana applies its own default (`10m`). Alias: `--reach-timeout` |
280
280
  | `--reach-analysis-memory-limit` | False | 8GB | Memory limit for each reachability analysis run, e.g. `512MB` or `8GB`. Omitted by default, so coana applies its own default (`8GB`). Alias: `--reach-memory-limit` |
281
281
  | `--reach-concurrency` | False | 1 | Control parallel analysis execution (must be >= 1). Omitted by default, so coana applies its own default. |
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "socketsecurity"
9
- version = "2.6.0"
9
+ version = "2.6.3"
10
10
  requires-python = ">= 3.11"
11
11
  license = {"file" = "LICENSE"}
12
12
  dependencies = [
@@ -17,8 +17,8 @@ dependencies = [
17
17
  "packaging==26.2",
18
18
  "python-dotenv==1.2.2",
19
19
  "socketdev==3.5.0",
20
- "beautifulsoup4==4.14.3",
21
- "markdown==3.10.2",
20
+ "beautifulsoup4==4.15.0",
21
+ "markdown==3.10.3",
22
22
  "brotli==1.2.0; platform_python_implementation == 'CPython'",
23
23
  "brotlicffi==1.2.0.1; platform_python_implementation != 'CPython'",
24
24
  ]
@@ -40,18 +40,18 @@ classifiers = [
40
40
 
41
41
  [project.optional-dependencies]
42
42
  test = [
43
- "pytest>=7.4.0",
44
- "pytest-cov>=4.1.0",
45
- "pytest-mock>=3.12.0",
46
- "pytest-asyncio>=0.23.0",
47
- "pytest-watch >=4.2.0"
43
+ "pytest==9.1.1",
44
+ "pytest-cov==7.1.0",
45
+ "pytest-mock==3.15.1",
46
+ "pytest-asyncio==1.4.0",
47
+ "pytest-watch==4.2.0"
48
48
  ]
49
49
  dev = [
50
- "ruff>=0.3.0",
51
- "twine", # for building
52
- "uv>=0.1.0", # for dependency management
53
- "pre-commit",
54
- "hatch"
50
+ "ruff==0.16.1",
51
+ "twine==7.0.0", # for building
52
+ "uv==0.12.0", # for dependency management
53
+ "pre-commit==4.6.1",
54
+ "hatch==1.17.1"
55
55
  ]
56
56
 
57
57
  [project.scripts]
@@ -1,3 +1,3 @@
1
1
  __author__ = 'socket.dev'
2
- __version__ = '2.6.0'
2
+ __version__ = '2.6.3'
3
3
  USER_AGENT = f'SocketPythonCLI/{__version__}'
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
15
15
  from socketsecurity.config import CliConfig
16
16
  from socketdev import socketdev
17
17
  from socketdev.exceptions import APIFailure
18
- from socketdev.fullscans import FullScanParams, SocketArtifact
18
+ from socketdev.fullscans import DiffArtifacts, FullScanParams, SocketArtifact
19
19
  from socketdev.org import Organization
20
20
  from socketdev.repos import RepositoryInfo
21
21
  import copy
@@ -92,6 +92,25 @@ FULL_SCAN_UPLOAD_BACKOFF_SCHEDULE_SECONDS = (10.0, 30.0, None)
92
92
  FULL_SCAN_UPLOAD_MAX_ATTEMPTS = len(FULL_SCAN_UPLOAD_BACKOFF_SCHEDULE_SECONDS)
93
93
  FULL_SCAN_UPLOAD_BACKOFF_JITTER_SECONDS = 2.0
94
94
 
95
+ # Diff-scan polling policy. The legacy scan comparison (fullscans.stream_diff) holds a
96
+ # single HTTP connection open, fully idle, while the backend computes the diff; network
97
+ # middleboxes with TCP idle timeouts (notably Azure NAT gateways, which default to
98
+ # 4 minutes) kill that connection with a RST, surfacing as an intermittent
99
+ # ConnectionResetError on large scans. The diff-scans flow instead creates a
100
+ # diff-scan resource and polls its cached endpoint with short bounded requests: the API
101
+ # answers 202 while the comparison is still computing and 200 with the result once it is
102
+ # ready, so no connection is ever idle long enough to be reaped.
103
+ #
104
+ # Each poll consumes 1 unit of API quota, so the interval backs off toward
105
+ # DIFF_SCAN_POLL_MAX_INTERVAL_SECONDS to stay quota-friendly on comparisons that take
106
+ # minutes to compute. The timeout is a backstop against a diff scan that never
107
+ # completes; on expiry (or any other failure of this flow) the caller falls back to the
108
+ # legacy streaming comparison rather than failing the scan outright.
109
+ DIFF_SCAN_POLL_INITIAL_INTERVAL_SECONDS = 5.0
110
+ DIFF_SCAN_POLL_MAX_INTERVAL_SECONDS = 30.0
111
+ DIFF_SCAN_POLL_BACKOFF_MULTIPLIER = 1.5
112
+ DIFF_SCAN_POLL_TIMEOUT_SECONDS = 30 * 60.0
113
+
95
114
 
96
115
  def _humanize_alert_type(alert_type: str) -> str:
97
116
  """Convert a camelCase/PascalCase alert type into a Title-Cased label.
@@ -154,9 +173,13 @@ class Core:
154
173
  """Returns SBOM artifacts for a full scan keyed by artifact ID."""
155
174
  response = self.sdk.fullscans.stream(self.config.org_slug, full_scan_id, use_types=True)
156
175
  if not response.success:
157
- log.debug(f"Failed to get SBOM data for full-scan {full_scan_id}")
158
- log.debug(response.message)
159
- return {}
176
+ # Raise instead of returning {} so a failed fetch surfaces as an
177
+ # API error (exit code 3 by default) rather than empty reports.
178
+ log.error(f"Failed to get SBOM data for full-scan {full_scan_id}")
179
+ log.error(response.message)
180
+ raise APIFailure(
181
+ f"Failed to get SBOM data for full-scan {full_scan_id}: {response.message}"
182
+ )
160
183
  if not hasattr(response, "artifacts") or not response.artifacts:
161
184
  return {}
162
185
  return response.artifacts
@@ -1303,6 +1326,120 @@ class Core:
1303
1326
 
1304
1327
  return packages
1305
1328
 
1329
+ def get_diff_scan_artifacts(
1330
+ self,
1331
+ head_full_scan_id: str,
1332
+ new_full_scan_id: str
1333
+ ) -> DiffArtifacts:
1334
+ """Compare two full scans via the diff-scans endpoints, polling for the result.
1335
+
1336
+ Creates a diff-scan resource from the two full scan IDs, then polls
1337
+ ``GET /orgs/{org}/diff-scans/{id}?cached=true`` until the API returns the
1338
+ computed comparison (200) instead of a processing status (202). Unlike the
1339
+ legacy ``fullscans.stream_diff`` call, no request is ever left idle while
1340
+ the backend computes, so the comparison survives network idle timeouts.
1341
+ See the DIFF_SCAN_POLL_* constants for the polling policy.
1342
+
1343
+ Requires an org token with the ``diff-scans:create``, ``diff-scans:list``
1344
+ and ``full-scans:list`` scopes; callers are expected to catch failures and
1345
+ fall back to the legacy streaming comparison.
1346
+
1347
+ Note that cached diff-scan responses always embed per-package license
1348
+ details (the API ignores ``omit_license_details`` when ``cached=true``),
1349
+ so unlike the legacy streaming comparison there is no lean-response
1350
+ option here; see the comment on ``poll_params`` below.
1351
+
1352
+ Args:
1353
+ head_full_scan_id: The before/base full scan ID
1354
+ new_full_scan_id: The after/head full scan ID
1355
+
1356
+ Returns:
1357
+ DiffArtifacts with the added/removed/unchanged/replaced/updated lists
1358
+ """
1359
+ create_params = {
1360
+ "before": head_full_scan_id,
1361
+ "after": new_full_scan_id,
1362
+ "description": f"Socket Security CLI v{__version__} scan comparison",
1363
+ }
1364
+ try:
1365
+ result = self.sdk.diffscans.create_from_ids(self.config.org_slug, create_params)
1366
+ diff_scan = result.get("diff_scan") or {}
1367
+ response_summary = result
1368
+ except APIFailure as error:
1369
+ if error.status_code != 409:
1370
+ raise
1371
+
1372
+ # Do not use on_duplicate=redirect here. The SDK follows that 302
1373
+ # automatically with a GET that lacks cached=true, which can leave
1374
+ # the connection idle while an existing diff scan is still computing.
1375
+ # Resolve the duplicate resource explicitly so every result fetch
1376
+ # continues through the bounded cached polling path below.
1377
+ existing = self.sdk.diffscans.list(
1378
+ self.config.org_slug,
1379
+ params={
1380
+ "before_full_scan_id": head_full_scan_id,
1381
+ "after_full_scan_id": new_full_scan_id,
1382
+ "per_page": 1,
1383
+ },
1384
+ )
1385
+ matches = existing.get("results") or []
1386
+ diff_scan = matches[0] if matches else {}
1387
+ response_summary = existing
1388
+
1389
+ diff_scan_id = diff_scan.get("id")
1390
+ if not diff_scan_id:
1391
+ raise Exception(
1392
+ "Error creating or resolving diff scan: "
1393
+ f"unexpected response: {str(response_summary)[:500]}"
1394
+ )
1395
+ artifacts_dict = diff_scan.get("artifacts")
1396
+
1397
+ # cached=true is the polling contract (202 while computing, 200 when
1398
+ # ready). The API ignores omit_license_details when cached=true - cached
1399
+ # results always embed license details - so there is no lean-response
1400
+ # option on this path (unlike stream_diff with
1401
+ # include_license_details=false, the lean-payload mitigation). If that extra
1402
+ # payload ever gets a response truncated on a huge dependency tree,
1403
+ # response.json() fails and the caller falls back to the legacy
1404
+ # streaming comparison, which still requests the lean payload.
1405
+ poll_params = {"cached": "true"}
1406
+ deadline = time.monotonic() + DIFF_SCAN_POLL_TIMEOUT_SECONDS
1407
+ interval = DIFF_SCAN_POLL_INITIAL_INTERVAL_SECONDS
1408
+ while artifacts_dict is None:
1409
+ try:
1410
+ response = self.sdk.diffscans.get(self.config.org_slug, diff_scan_id, params=poll_params)
1411
+ except APIFailure as error:
1412
+ if not error.is_transient_error():
1413
+ raise
1414
+ # A dropped/timed-out poll is retryable: the diff scan keeps
1415
+ # computing server-side regardless of what happens to any one poll.
1416
+ log.warning(
1417
+ f"Transient error polling diff scan {diff_scan_id} "
1418
+ f"({type(error).__name__}), retrying in {interval:.0f}s"
1419
+ )
1420
+ response = {"status": "processing"}
1421
+ if response.get("status") != "processing":
1422
+ scan = response.get("diff_scan") or {}
1423
+ if scan.get("artifacts") is None:
1424
+ raise Exception(
1425
+ f"Error fetching diff scan {diff_scan_id}: unexpected response: {str(response)[:500]}"
1426
+ )
1427
+ artifacts_dict = scan["artifacts"]
1428
+ break
1429
+ if time.monotonic() >= deadline:
1430
+ raise Exception(
1431
+ f"Timed out waiting for diff scan {diff_scan_id} after "
1432
+ f"{DIFF_SCAN_POLL_TIMEOUT_SECONDS:.0f} seconds"
1433
+ )
1434
+ log.debug(f"Diff scan {diff_scan_id} still processing, polling again in {interval:.0f}s")
1435
+ time.sleep(interval)
1436
+ interval = min(interval * DIFF_SCAN_POLL_BACKOFF_MULTIPLIER, DIFF_SCAN_POLL_MAX_INTERVAL_SECONDS)
1437
+
1438
+ return DiffArtifacts.from_dict({
1439
+ key: artifacts_dict.get(key) or []
1440
+ for key in ("added", "removed", "unchanged", "replaced", "updated")
1441
+ })
1442
+
1306
1443
  def get_added_and_removed_packages(
1307
1444
  self,
1308
1445
  head_full_scan_id: str,
@@ -1315,8 +1452,12 @@ class Core:
1315
1452
  Args:
1316
1453
  head_full_scan_id: Previous scan (maybe None if first scan)
1317
1454
  new_full_scan_id: New scan just created
1318
- include_license_details: Whether to ask the diff endpoint to embed
1319
- per-package license attribution/details in the response.
1455
+ include_license_details: Whether to ask the *legacy streaming* diff
1456
+ endpoint to embed per-package license attribution/details in the
1457
+ response. Only consulted on the fallback path: the primary
1458
+ diff-scans path always receives embedded license details, since
1459
+ the API ignores ``omit_license_details`` for cached reads (see
1460
+ get_diff_scan_artifacts).
1320
1461
 
1321
1462
  Defaults to ``False`` on purpose. The diff endpoint exists to
1322
1463
  compare alerts between two scans; the license fields it can embed
@@ -1343,39 +1484,55 @@ class Core:
1343
1484
 
1344
1485
  log.info(f"Comparing scans - Head scan ID: {head_full_scan_id}, New scan ID: {new_full_scan_id}")
1345
1486
  diff_start = time.time()
1487
+ diff_artifacts = None
1346
1488
  try:
1347
- diff_report = (
1348
- self.sdk.fullscans.stream_diff(
1349
- self.config.org_slug,
1350
- head_full_scan_id,
1351
- new_full_scan_id,
1352
- use_types=True,
1353
- include_license_details=str(include_license_details).lower()
1354
- ).data
1489
+ diff_artifacts = self.get_diff_scan_artifacts(
1490
+ head_full_scan_id,
1491
+ new_full_scan_id
1355
1492
  )
1356
- except APIFailure as e:
1357
- log.error(f"API Error: {e}")
1358
- if self.cli_config and self.cli_config.disable_blocking:
1359
- sys.exit(0)
1360
- sys.exit(1)
1361
- except Exception as e:
1362
- import traceback
1363
- log.error(f"Error getting diff report: {str(e)}")
1364
- log.error(f"Stack trace:\n{traceback.format_exc()}")
1365
- raise
1493
+ except Exception as error:
1494
+ # SDK error messages can span many lines (path + response headers); the
1495
+ # first line carries the status, which is all the warning needs.
1496
+ error_summary = str(error).strip().splitlines()[0] if str(error).strip() else ""
1497
+ log.warning(
1498
+ f"Diff scan comparison failed with {type(error).__name__}({error_summary}), "
1499
+ "falling back to the streaming scan comparison"
1500
+ )
1501
+
1502
+ if diff_artifacts is None:
1503
+ try:
1504
+ diff_artifacts = (
1505
+ self.sdk.fullscans.stream_diff(
1506
+ self.config.org_slug,
1507
+ head_full_scan_id,
1508
+ new_full_scan_id,
1509
+ use_types=True,
1510
+ include_license_details=str(include_license_details).lower()
1511
+ ).data.artifacts
1512
+ )
1513
+ except APIFailure as e:
1514
+ log.error(f"API Error: {e}")
1515
+ if self.cli_config and self.cli_config.disable_blocking:
1516
+ sys.exit(0)
1517
+ sys.exit(1)
1518
+ except Exception as e:
1519
+ import traceback
1520
+ log.error(f"Error getting diff report: {str(e)}")
1521
+ log.error(f"Stack trace:\n{traceback.format_exc()}")
1522
+ raise
1366
1523
 
1367
1524
  diff_end = time.time()
1368
1525
  log.info(f"Diff Report Gathered in {diff_end - diff_start:.2f} seconds")
1369
1526
  log.info("Diff report artifact counts:")
1370
- log.info(f"Added: {len(diff_report.artifacts.added)}")
1371
- log.info(f"Removed: {len(diff_report.artifacts.removed)}")
1372
- log.info(f"Unchanged: {len(diff_report.artifacts.unchanged)}")
1373
- log.info(f"Replaced: {len(diff_report.artifacts.replaced)}")
1374
- log.info(f"Updated: {len(diff_report.artifacts.updated)}")
1375
-
1376
- added_artifacts = diff_report.artifacts.added + diff_report.artifacts.updated
1377
- removed_artifacts = diff_report.artifacts.removed + diff_report.artifacts.replaced
1378
- unchanged_artifacts = diff_report.artifacts.unchanged
1527
+ log.info(f"Added: {len(diff_artifacts.added)}")
1528
+ log.info(f"Removed: {len(diff_artifacts.removed)}")
1529
+ log.info(f"Unchanged: {len(diff_artifacts.unchanged)}")
1530
+ log.info(f"Replaced: {len(diff_artifacts.replaced)}")
1531
+ log.info(f"Updated: {len(diff_artifacts.updated)}")
1532
+
1533
+ added_artifacts = diff_artifacts.added + diff_artifacts.updated
1534
+ removed_artifacts = diff_artifacts.removed + diff_artifacts.replaced
1535
+ unchanged_artifacts = diff_artifacts.unchanged
1379
1536
 
1380
1537
  added_packages: Dict[str, Package] = {}
1381
1538
  removed_packages: Dict[str, Package] = {}
@@ -18,7 +18,7 @@ log = logging.getLogger(__name__)
18
18
  # Pinned @coana-tech/cli version. Bumped deliberately per Python CLI release so the
19
19
  # reachability engine version only changes through a standard pip upgrade (advance notice).
20
20
  # Pass --reach-version latest to opt into the newest published version instead.
21
- DEFAULT_COANA_CLI_VERSION: Final = "15.10.3"
21
+ DEFAULT_COANA_CLI_VERSION: Final = "15.10.4"
22
22
 
23
23
  # Resolved @coana-tech/cli script paths from the npm-install fallback, keyed by version.
24
24
  # Lives for the process lifetime so repeated fallback invocations install only once
@@ -55,7 +55,7 @@ class ReachabilityAnalyzer:
55
55
 
56
56
  def _resolve_coana_package_spec(self, version: Optional[str] = None) -> str:
57
57
  """
58
- Resolve the @coana-tech/cli package spec to run (e.g. '@coana-tech/cli@15.10.3').
58
+ Resolve the @coana-tech/cli package spec to run (e.g. '@coana-tech/cli@15.10.4').
59
59
 
60
60
  Args:
61
61
  version: Coana CLI version to use.
@@ -64,7 +64,7 @@ class ReachabilityAnalyzer:
64
64
  - '<semver>': that exact version.
65
65
 
66
66
  Returns:
67
- str: The package specifier to use with npx (e.g. '@coana-tech/cli@15.10.3').
67
+ str: The package specifier to use with npx (e.g. '@coana-tech/cli@15.10.4').
68
68
  """
69
69
  return f"@coana-tech/cli@{self._resolve_coana_version(version)}"
70
70
 
@@ -87,6 +87,22 @@ def stream_diff_response(data_dir, load_json):
87
87
  })
88
88
 
89
89
 
90
+ @pytest.fixture
91
+ def diff_scan_get_response(data_dir, load_json):
92
+ """GET /orgs/{org}/diff-scans/{id} response built from the stream_diff fixture.
93
+
94
+ The diff-scans endpoint returns the same artifact shape as the legacy
95
+ streaming diff, wrapped in a diff_scan object.
96
+ """
97
+ json_data = load_json(data_dir / "fullscans" / "diff" / "stream_diff.json")
98
+ return {
99
+ "diff_scan": {
100
+ "id": "diff-scan-123",
101
+ "artifacts": json_data["data"]["artifacts"],
102
+ }
103
+ }
104
+
105
+
90
106
 
91
107
 
92
108
 
@@ -138,6 +154,7 @@ def mock_sdk_with_responses(
138
154
  new_scan_metadata,
139
155
  new_scan_stream,
140
156
  stream_diff_response,
157
+ diff_scan_get_response,
141
158
  create_full_scan_response,
142
159
  ):
143
160
  sdk = mock_socket_sdk.return_value
@@ -173,4 +190,8 @@ def mock_sdk_with_responses(
173
190
  lambda org_slug, head_id, new_id, **kwargs: stream_diff_response
174
191
  )
175
192
 
193
+ # Diff-scans endpoints (primary scan-comparison path)
194
+ sdk.diffscans.create_from_ids.return_value = {"diff_scan": {"id": "diff-scan-123"}}
195
+ sdk.diffscans.get.return_value = diff_scan_get_response
196
+
176
197
  return sdk