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.
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/actions/setup-docker/action.yml +3 -3
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/actions/setup-sfw/action.yml +1 -1
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/e2e-test.yml +1 -1
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/package-check.yml +1 -1
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/pr-preview.yml +1 -1
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/python-tests.yml +2 -2
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/release.yml +2 -2
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/CHANGELOG.md +50 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/PKG-INFO +13 -13
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/docs/cli-reference.md +1 -1
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/pyproject.toml +13 -13
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/__init__.py +1 -1
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/__init__.py +190 -33
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/tools/reachability.py +3 -3
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/conftest.py +21 -0
- socketsecurity-2.6.3/tests/core/test_diff_scan_polling.py +122 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/test_sdk_methods.py +45 -13
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/uv.lock +46 -46
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/CODEOWNERS +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/actions/setup-hatch/action.yml +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/dependabot.yml +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/dependency-review.yml +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/docker-stable.yml +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/workflows/version-check.yml +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.github/zizmor.yml +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.gitignore +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.hooks/sync_version.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.pre-commit-config.yaml +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/.python-version +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/CONTRIBUTING.md +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/Dockerfile +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/Dockerfile.preview +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/LICENSE +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/Makefile +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/README.md +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/docs/ci-cd.md +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/docs/development.md +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/docs/troubleshooting.md +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/examples/config/sarif-dashboard-parity.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/examples/config/sarif-dashboard-parity.toml +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/examples/config/sarif-diff-ci-cd.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/examples/config/sarif-diff-ci-cd.toml +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/examples/config/sarif-instance-detail.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/examples/config/sarif-instance-detail.toml +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/instructions/gitlab-commit-status/uat.md +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/pytest.ini +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/scripts/build_container.sh +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/scripts/build_container_flexible.sh +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/scripts/deploy-test-docker.sh +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/scripts/deploy-test-pypi.sh +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/scripts/docker-entrypoint.sh +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/scripts/run.sh +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/session.md +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socket.yml +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/config.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/alert_selection.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/classes.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/cli_client.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/cli_run.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/exceptions.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/git_interface.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/helper/__init__.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/helper/socket_facts_loader.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/lazy_file_loader.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/log_uploader.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/logging.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/messages.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/resource_utils.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/scm/__init__.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/scm/base.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/scm/client.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/scm/github.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/scm/gitlab.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/scm_comments.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/socket_config.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/streaming.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/core/utils.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/fossa_compat.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/output.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/__init__.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/base.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/formatters/__init__.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/formatters/slack.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/jira.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/manager.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/slack.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/teams.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/plugins/webhook.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/socketsecurity/socketcli.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/__init__.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/create_diff_input.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/test_diff_alerts.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/test_diff_generation.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/test_facts_compression.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/test_has_manifest_files.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/test_package_and_alerts.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/core/test_supporting_methods.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/create_response.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/diff/stream_diff.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/head_scan/metadata.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/new_scan/metadata.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/repos/repo_info_error.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/repos/repo_info_no_head.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/repos/repo_info_success.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/data/settings/security-policy.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/fixtures/simple-npm/index.js +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/fixtures/simple-npm/package.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/fixtures/simple-pypi/requirements.txt +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/reach-facts-probe.sh +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/validate-gitlab.sh +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/validate-json.sh +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/validate-reachability.sh +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/validate-sarif.sh +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/e2e/validate-scan.sh +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/fixtures/fossa/README.md +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/fixtures/fossa/fossa-analyze-empty.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/fixtures/fossa/fossa-analyze-populated.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/fixtures/fossa/fossa-sbom-empty-deep.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/fixtures/fossa/fossa-sbom-populated.json +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/__init__.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_alert_selection.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_cli_config.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_cli_run.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_client.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_config.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_dependency_overview.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_disable_ignore.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_exclude_paths.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_fossa_compat.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_fossa_parity.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_full_scan_retry.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_gitlab_auth.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_gitlab_auth_fallback.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_gitlab_commit_status.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_gitlab_format.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_ignore_telemetry_filtering.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_include_dirs.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_log_uploader.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_output.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_reachability.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_slack_plugin.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_socketcli.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_streaming.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/tests/unit/test_tier1_finalize.py +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/workflows/bitbucket-pipelines.yml +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/workflows/buildkite.yml +0 -0
- {socketsecurity-2.6.0 → socketsecurity-2.6.3}/workflows/github-actions.yml +0 -0
- {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@
|
|
22
|
+
- uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
|
23
23
|
if: inputs.enable-qemu == 'true'
|
|
24
|
-
- uses: docker/setup-buildx-action@
|
|
25
|
-
- uses: docker/login-action@
|
|
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@
|
|
30
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
31
31
|
with:
|
|
32
32
|
python-version: "3.12"
|
|
33
33
|
|
|
@@ -107,7 +107,7 @@ jobs:
|
|
|
107
107
|
fetch-depth: 0
|
|
108
108
|
persist-credentials: false
|
|
109
109
|
|
|
110
|
-
- uses: actions/setup-python@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
51
|
-
Requires-Dist: twine; extra == 'dev'
|
|
52
|
-
Requires-Dist: uv
|
|
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
|
|
55
|
-
Requires-Dist: pytest-cov
|
|
56
|
-
Requires-Dist: pytest-mock
|
|
57
|
-
Requires-Dist: pytest-watch
|
|
58
|
-
Requires-Dist: pytest
|
|
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.
|
|
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.
|
|
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.
|
|
21
|
-
"markdown==3.10.
|
|
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
|
|
44
|
-
"pytest-cov
|
|
45
|
-
"pytest-mock
|
|
46
|
-
"pytest-asyncio
|
|
47
|
-
"pytest-watch
|
|
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
|
|
51
|
-
"twine", # for building
|
|
52
|
-
"uv
|
|
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]
|
|
@@ -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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
|
1319
|
-
per-package license attribution/details in the
|
|
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
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
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
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
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(
|
|
1371
|
-
log.info(f"Removed: {len(
|
|
1372
|
-
log.info(f"Unchanged: {len(
|
|
1373
|
-
log.info(f"Replaced: {len(
|
|
1374
|
-
log.info(f"Updated: {len(
|
|
1375
|
-
|
|
1376
|
-
added_artifacts =
|
|
1377
|
-
removed_artifacts =
|
|
1378
|
-
unchanged_artifacts =
|
|
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.
|
|
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.
|
|
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.
|
|
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
|