socketsecurity 2.6.11__tar.gz → 2.7.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.
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/CHANGELOG.md +51 -1
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/PKG-INFO +2 -2
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/pyproject.toml +2 -2
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/__init__.py +1 -1
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/__init__.py +107 -23
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/messages.py +97 -13
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/scm_comments.py +48 -9
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/socketcli.py +36 -16
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/core/test_facts_compression.py +1 -1
- socketsecurity-2.7.0/tests/core/test_invalid_facts_marker.py +278 -0
- socketsecurity-2.7.0/tests/unit/test_pr_comment_rendering.py +304 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_socketcli.py +44 -1
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/uv.lock +23 -23
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/CODEOWNERS +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/actions/setup-docker/action.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/actions/setup-hatch/action.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/actions/setup-sfw/action.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/dependabot.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/workflows/dependency-review.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/workflows/docker-stable.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/workflows/e2e-test.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/workflows/package-check.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/workflows/pr-preview.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/workflows/python-tests.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/workflows/release.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/workflows/version-check.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.github/zizmor.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.gitignore +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.hooks/sync_version.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.pre-commit-config.yaml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/.python-version +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/CONTRIBUTING.md +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/Dockerfile +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/Dockerfile.preview +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/LICENSE +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/Makefile +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/README.md +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/benchmarks/manifest_discovery.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/docs/ci-cd.md +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/docs/cli-reference.md +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/docs/development.md +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/docs/troubleshooting.md +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/examples/config/sarif-dashboard-parity.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/examples/config/sarif-dashboard-parity.toml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/examples/config/sarif-diff-ci-cd.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/examples/config/sarif-diff-ci-cd.toml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/examples/config/sarif-instance-detail.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/examples/config/sarif-instance-detail.toml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/instructions/gitlab-commit-status/uat.md +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/pytest.ini +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/scripts/build_container.sh +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/scripts/build_container_flexible.sh +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/scripts/deploy-test-docker.sh +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/scripts/deploy-test-pypi.sh +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/scripts/docker-entrypoint.sh +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/scripts/run.sh +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/session.md +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socket.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/config.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/alert_selection.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/classes.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/cli_client.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/cli_run.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/exceptions.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/git_interface.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/helper/__init__.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/helper/socket_facts_loader.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/lazy_file_loader.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/log_uploader.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/logging.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/resource_utils.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/scm/__init__.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/scm/base.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/scm/client.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/scm/github.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/scm/gitlab.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/socket_config.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/streaming.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/tools/reachability.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/core/utils.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/fossa_compat.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/output.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/plugins/__init__.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/plugins/base.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/plugins/formatters/__init__.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/plugins/formatters/slack.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/plugins/jira.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/plugins/manager.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/plugins/slack.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/plugins/teams.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/socketsecurity/plugins/webhook.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/__init__.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/core/conftest.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/core/create_diff_input.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/core/test_diff_alerts.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/core/test_diff_generation.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/core/test_diff_scan_polling.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/core/test_has_manifest_files.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/core/test_package_and_alerts.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/core/test_sdk_methods.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/core/test_supporting_methods.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/data/fullscans/create_response.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/data/fullscans/diff/stream_diff.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/data/fullscans/head_scan/metadata.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/data/fullscans/new_scan/metadata.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/data/repos/repo_info_error.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/data/repos/repo_info_no_head.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/data/repos/repo_info_success.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/data/settings/security-policy.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/e2e/fixtures/simple-npm/index.js +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/e2e/fixtures/simple-npm/package.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/e2e/fixtures/simple-pypi/requirements.txt +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/e2e/reach-facts-probe.sh +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/e2e/validate-gitlab.sh +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/e2e/validate-json.sh +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/e2e/validate-reachability.sh +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/e2e/validate-sarif.sh +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/e2e/validate-scan.sh +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/fixtures/fossa/README.md +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/fixtures/fossa/fossa-analyze-empty.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/fixtures/fossa/fossa-analyze-populated.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/fixtures/fossa/fossa-sbom-empty-deep.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/fixtures/fossa/fossa-sbom-populated.json +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/__init__.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_alert_selection.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_cli_config.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_cli_run.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_client.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_config.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_dependency_overview.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_disable_ignore.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_exclude_paths.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_fossa_compat.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_fossa_parity.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_full_scan_retry.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_git_interface.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_github_buildkite_config.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_gitlab_auth.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_gitlab_auth_fallback.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_gitlab_commit_status.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_gitlab_format.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_ignore_telemetry_filtering.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_include_dirs.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_log_uploader.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_manifest_discovery.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_output.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_reachability.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_slack_plugin.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_streaming.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/tests/unit/test_tier1_finalize.py +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/workflows/bitbucket-pipelines.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/workflows/buildkite.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/workflows/github-actions.yml +0 -0
- {socketsecurity-2.6.11 → socketsecurity-2.7.0}/workflows/gitlab-ci.yml +0 -0
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.7.0
|
|
4
|
+
|
|
5
|
+
### Fixed: unreadable reachability facts no longer report a blocking package
|
|
6
|
+
|
|
7
|
+
- Scans with no supported manifest files uploaded a zero-byte `.socket.facts.json`
|
|
8
|
+
placeholder. The API cannot parse that, and answers by adding a
|
|
9
|
+
`generic/invalid-socket-facts@1.0.0` artifact to the scan, which the CLI then reported
|
|
10
|
+
as a new blocking package with no manifest file and no introducing dependency —
|
|
11
|
+
failing the run and, on pull requests, leaving a security comment that could not be
|
|
12
|
+
acted on. The placeholder is now an empty but well-formed facts document.
|
|
13
|
+
- When the API does report `generic/invalid-socket-facts` (a diagnostic for a facts file
|
|
14
|
+
it could not parse, not a real dependency), the CLI now excludes it from scan results
|
|
15
|
+
and logs a warning instead. It no longer blocks a run, appears in reports, or triggers
|
|
16
|
+
a pull request comment.
|
|
17
|
+
- Each placeholder is written to its own temporary directory. Two CLI runs sharing a
|
|
18
|
+
temporary directory previously used the same path and could remove each other's
|
|
19
|
+
placeholder mid-upload.
|
|
20
|
+
|
|
21
|
+
### Fixed: pull request comments no longer show orphaned tags or an empty table
|
|
22
|
+
|
|
23
|
+
- Optional sections that rendered as empty, such as the ignore instructions
|
|
24
|
+
suppressed by `--disable-ignore`, left a whitespace-only line in the alerts
|
|
25
|
+
table. That line closed the surrounding HTML block, and the indented
|
|
26
|
+
`</blockquote></details>` tags after it were rendered as a literal code block.
|
|
27
|
+
Generated comment markup now omits blank lines and stays under the indentation
|
|
28
|
+
that starts a code block.
|
|
29
|
+
- Alert descriptions, suggestions and license findings are collapsed onto a
|
|
30
|
+
single line so multi-line API text cannot break the table markup either.
|
|
31
|
+
- When a pull request has no alerts left to report, the security comment is
|
|
32
|
+
replaced with a short confirmation instead of keeping the "Caution" banner
|
|
33
|
+
above a table with no rows. This happens both when a later commit resolves
|
|
34
|
+
every alert and when every alert is ignored by comment. The comment marker is
|
|
35
|
+
preserved, so a commit that reintroduces an alert updates the same comment
|
|
36
|
+
rather than posting a second one.
|
|
37
|
+
- `@SocketSecurity ignore-all` now applies to comments written by CLI versions
|
|
38
|
+
before 2.0.55, which use the older Markdown alerts table. The check was made
|
|
39
|
+
once per ignore command, and an ignore-all comment produces none, so no rows
|
|
40
|
+
were removed.
|
|
41
|
+
|
|
42
|
+
### Fixed: `--disable-security-issue` and `--disable-overview` now suppress the comment entirely
|
|
43
|
+
|
|
44
|
+
- Both flags were checked only after testing whether a comment of that type was
|
|
45
|
+
already on the pull request, so they suppressed the first post and then
|
|
46
|
+
updated that comment on every later run. `--disable-security-issue` in
|
|
47
|
+
particular kept refreshing an existing comment with the full alerts table.
|
|
48
|
+
- The flags now mean the CLI does not manage that comment at all. An existing
|
|
49
|
+
comment is left untouched rather than being rewritten, since a body claiming
|
|
50
|
+
no alerts would be inaccurate when reporting is merely switched off.
|
|
51
|
+
- The decision moved into `should_write_comment()` so it is covered directly by
|
|
52
|
+
tests.
|
|
53
|
+
|
|
3
54
|
## 2.6.11
|
|
4
55
|
|
|
5
56
|
### Changed: bump pinned @coana-tech/cli to 15.10.32
|
|
@@ -31,7 +82,6 @@
|
|
|
31
82
|
- Bumped the pinned reachability engine (`@coana-tech/cli`) from `15.10.23` to
|
|
32
83
|
`15.10.25`. See the [Coana changelogs](https://docs.coana.tech/changelogs) for
|
|
33
84
|
engine changes.
|
|
34
|
-
|
|
35
85
|
## 2.6.7
|
|
36
86
|
|
|
37
87
|
### Changed: bump pinned @coana-tech/cli to 15.10.23
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: socketsecurity
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.7.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>
|
|
@@ -47,7 +47,7 @@ Requires-Dist: socketdev==3.5.0
|
|
|
47
47
|
Provides-Extra: dev
|
|
48
48
|
Requires-Dist: hatch==1.18.0; extra == 'dev'
|
|
49
49
|
Requires-Dist: pre-commit==4.6.2; extra == 'dev'
|
|
50
|
-
Requires-Dist: ruff==0.16.
|
|
50
|
+
Requires-Dist: ruff==0.16.4; extra == 'dev'
|
|
51
51
|
Requires-Dist: twine==7.0.0; extra == 'dev'
|
|
52
52
|
Requires-Dist: uv==0.12.5; extra == 'dev'
|
|
53
53
|
Provides-Extra: test
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "socketsecurity"
|
|
9
|
-
version = "2.
|
|
9
|
+
version = "2.7.0"
|
|
10
10
|
requires-python = ">= 3.11"
|
|
11
11
|
license = {"file" = "LICENSE"}
|
|
12
12
|
dependencies = [
|
|
@@ -47,7 +47,7 @@ test = [
|
|
|
47
47
|
"pytest-watch==4.2.0"
|
|
48
48
|
]
|
|
49
49
|
dev = [
|
|
50
|
-
"ruff==0.16.
|
|
50
|
+
"ruff==0.16.4",
|
|
51
51
|
"twine==7.0.0", # for building
|
|
52
52
|
"uv==0.12.5", # for dependency management
|
|
53
53
|
"pre-commit==4.6.2",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import atexit
|
|
1
2
|
import copy
|
|
2
3
|
import fnmatch
|
|
3
4
|
import importlib
|
|
@@ -6,6 +7,7 @@ import logging
|
|
|
6
7
|
import os
|
|
7
8
|
import random
|
|
8
9
|
import re
|
|
10
|
+
import shutil
|
|
9
11
|
import sys
|
|
10
12
|
import tarfile
|
|
11
13
|
import tempfile
|
|
@@ -69,6 +71,16 @@ SOCKET_FACTS_BROTLI_LGWIN = 24
|
|
|
69
71
|
# Stream the facts file in 1 MiB chunks so large files aren't held fully in memory.
|
|
70
72
|
SOCKET_FACTS_BROTLI_CHUNK_SIZE = 1024 * 1024
|
|
71
73
|
|
|
74
|
+
# Placeholder facts document (see empty_head_scan_file). A zero-byte file does not parse,
|
|
75
|
+
# and the API answers an unparseable facts file with the marker artifact below.
|
|
76
|
+
SOCKET_FACTS_EMPTY_DOCUMENT = '{"components": []}'
|
|
77
|
+
|
|
78
|
+
# Synthetic artifact the API adds when an uploaded ``.socket.facts.json`` could not be
|
|
79
|
+
# parsed. A diagnostic, not a dependency, so it is dropped from scan results and reported
|
|
80
|
+
# as a warning instead.
|
|
81
|
+
INVALID_FACTS_MARKER_TYPE = "generic"
|
|
82
|
+
INVALID_FACTS_MARKER_NAME = "invalid-socket-facts"
|
|
83
|
+
|
|
72
84
|
# Full application reachability finalize retry policy. The finalize call links the reachability
|
|
73
85
|
# scan to the full scan and can fail transiently (network/API blips); a few backoff retries make it robust.
|
|
74
86
|
TIER1_FINALIZE_MAX_ATTEMPTS = 3
|
|
@@ -108,6 +120,17 @@ DIFF_SCAN_POLL_MAX_INTERVAL_SECONDS = 10.0
|
|
|
108
120
|
DIFF_SCAN_POLL_BACKOFF_MULTIPLIER = 1.5
|
|
109
121
|
DIFF_SCAN_POLL_TIMEOUT_SECONDS = 30 * 60.0
|
|
110
122
|
|
|
123
|
+
# Temp dirs holding placeholder facts files (see Core.empty_head_scan_file). Call sites unlink
|
|
124
|
+
# the file itself once the upload finishes; the now-empty directory is removed at process exit
|
|
125
|
+
# so a run that raises mid-scan doesn't leak one.
|
|
126
|
+
_PLACEHOLDER_FACTS_DIRS: List[str] = []
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@atexit.register
|
|
130
|
+
def _cleanup_placeholder_facts_dirs() -> None:
|
|
131
|
+
for placeholder_dir in _PLACEHOLDER_FACTS_DIRS:
|
|
132
|
+
shutil.rmtree(placeholder_dir, ignore_errors=True)
|
|
133
|
+
|
|
111
134
|
|
|
112
135
|
def _humanize_alert_type(alert_type: str) -> str:
|
|
113
136
|
"""Convert a camelCase/PascalCase alert type into a Title-Cased label.
|
|
@@ -209,13 +232,54 @@ class Core:
|
|
|
209
232
|
)
|
|
210
233
|
if not hasattr(response, "artifacts") or not response.artifacts:
|
|
211
234
|
return {}
|
|
212
|
-
|
|
235
|
+
artifacts = {
|
|
236
|
+
artifact_id: artifact
|
|
237
|
+
for artifact_id, artifact in response.artifacts.items()
|
|
238
|
+
if not Core.is_invalid_facts_marker(artifact)
|
|
239
|
+
}
|
|
240
|
+
Core.warn_if_invalid_facts_marker(len(artifacts) != len(response.artifacts))
|
|
241
|
+
return artifacts
|
|
213
242
|
|
|
214
243
|
def get_sbom_data_list(self, artifacts_dict: Dict[str, SocketArtifact]) -> list[SocketArtifact]:
|
|
215
244
|
"""Converts artifacts dictionary to a list."""
|
|
216
245
|
return list(artifacts_dict.values())
|
|
217
246
|
|
|
247
|
+
@staticmethod
|
|
248
|
+
def is_invalid_facts_marker(artifact) -> bool:
|
|
249
|
+
"""True for the API's ``generic/invalid-socket-facts`` unparseable-facts marker.
|
|
250
|
+
|
|
251
|
+
Treated as a package it becomes a blocking alert with an empty "Introduced by" and
|
|
252
|
+
"Manifest File" that no developer can act on, so callers drop it and report the parse
|
|
253
|
+
failure through ``warn_if_invalid_facts_marker`` instead.
|
|
218
254
|
|
|
255
|
+
Matches any version; the API pins it to 1.0.0 but the version carries no meaning.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
artifact: A ``SocketArtifact`` or diff artifact (anything with ``type``/``name``).
|
|
259
|
+
|
|
260
|
+
Returns:
|
|
261
|
+
True if the artifact is the marker rather than a real package.
|
|
262
|
+
"""
|
|
263
|
+
return (
|
|
264
|
+
getattr(artifact, "type", None) == INVALID_FACTS_MARKER_TYPE
|
|
265
|
+
and getattr(artifact, "name", None) == INVALID_FACTS_MARKER_NAME
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
@staticmethod
|
|
269
|
+
def warn_if_invalid_facts_marker(found: bool) -> None:
|
|
270
|
+
"""Log the parse failure that ``is_invalid_facts_marker`` stands for.
|
|
271
|
+
|
|
272
|
+
Dropping the marker silently would hide a real, if non-blocking, problem: the scan ran
|
|
273
|
+
without the reachability data it was supposed to carry.
|
|
274
|
+
"""
|
|
275
|
+
if not found:
|
|
276
|
+
return
|
|
277
|
+
log.warning(
|
|
278
|
+
"Socket could not parse the uploaded .socket.facts.json, so reachability facts "
|
|
279
|
+
"were not applied to this scan. Ignoring the "
|
|
280
|
+
f"{INVALID_FACTS_MARKER_TYPE}/{INVALID_FACTS_MARKER_NAME} marker returned for it; "
|
|
281
|
+
"other scan results are unaffected."
|
|
282
|
+
)
|
|
219
283
|
|
|
220
284
|
def create_sbom_output(self, diff: Diff) -> dict:
|
|
221
285
|
"""Creates CycloneDX output for a given diff."""
|
|
@@ -809,20 +873,31 @@ class Core:
|
|
|
809
873
|
@staticmethod
|
|
810
874
|
def empty_head_scan_file() -> List[str]:
|
|
811
875
|
"""
|
|
812
|
-
Creates a temporary
|
|
813
|
-
|
|
876
|
+
Creates a temporary placeholder manifest for scans with no manifest files.
|
|
877
|
+
|
|
878
|
+
Used for baseline scans when a repository has no head scan yet, and for the new scan
|
|
879
|
+
when no supported manifest files were found. The API rejects unsupported filenames, so
|
|
880
|
+
the placeholder must be named ``.socket.facts.json`` - which means it must also parse
|
|
881
|
+
as a facts document. A zero-byte file does not, and the API answers that by adding a
|
|
882
|
+
blocking ``generic/invalid-socket-facts@1.0.0`` artifact to the scan.
|
|
883
|
+
|
|
884
|
+
Each call gets its own temp directory. The path used to be a fixed
|
|
885
|
+
``$TMPDIR/.socket.facts.json``, so two runs sharing a temp dir could delete or
|
|
886
|
+
truncate each other's placeholder mid-upload.
|
|
887
|
+
|
|
814
888
|
Returns:
|
|
815
|
-
List containing path to a temporary
|
|
889
|
+
List containing path to a temporary placeholder facts file
|
|
816
890
|
"""
|
|
817
|
-
#
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
891
|
+
# Own directory per call so concurrent runs can't clobber each other's placeholder;
|
|
892
|
+
# the basename must stay exactly SOCKET_FACTS_FILENAME to pass the API's validator.
|
|
893
|
+
temp_dir = tempfile.mkdtemp(prefix='socket_baseline_')
|
|
894
|
+
_PLACEHOLDER_FACTS_DIRS.append(temp_dir)
|
|
895
|
+
temp_path = os.path.join(temp_dir, SOCKET_FACTS_FILENAME)
|
|
896
|
+
|
|
897
|
+
with open(temp_path, 'w') as f:
|
|
898
|
+
f.write(SOCKET_FACTS_EMPTY_DOCUMENT)
|
|
899
|
+
|
|
900
|
+
log.debug(f"Created temporary placeholder facts file for baseline scan: {temp_path}")
|
|
826
901
|
return [temp_path]
|
|
827
902
|
|
|
828
903
|
def finalize_tier1_scan(self, full_scan_id: str, facts_file_path: str) -> bool:
|
|
@@ -959,7 +1034,7 @@ class Core:
|
|
|
959
1034
|
exactly ``.socket.facts.json.br``, so compressing here keeps a large facts file under
|
|
960
1035
|
the server's per-file size cap without changing the stored result. Files whose
|
|
961
1036
|
basename is not exactly ``.socket.facts.json`` are left untouched (the server only
|
|
962
|
-
matches that exact name), as are
|
|
1037
|
+
matches that exact name), as are zero-byte files.
|
|
963
1038
|
|
|
964
1039
|
Compression never blocks an upload: if it fails for any reason (missing optional
|
|
965
1040
|
``brotli`` dependency, unwritable directory, etc.) the original plain file is used.
|
|
@@ -1780,16 +1855,25 @@ class Core:
|
|
|
1780
1855
|
|
|
1781
1856
|
diff_end = time.time()
|
|
1782
1857
|
log.info(f"Diff Report Gathered in {diff_end - diff_start:.2f} seconds")
|
|
1858
|
+
|
|
1859
|
+
# Left in, the invalid-socket-facts marker reads as a newly added blocking package.
|
|
1860
|
+
# Drop it from every bucket before the counts below, which should describe what the
|
|
1861
|
+
# CLI actually reports on.
|
|
1862
|
+
marker_found = False
|
|
1863
|
+
buckets: Dict[str, List] = {}
|
|
1864
|
+
for name in ("added", "removed", "unchanged", "replaced", "updated"):
|
|
1865
|
+
bucket = getattr(diff_artifacts, name)
|
|
1866
|
+
buckets[name] = [a for a in bucket if not Core.is_invalid_facts_marker(a)]
|
|
1867
|
+
marker_found = marker_found or len(buckets[name]) != len(bucket)
|
|
1868
|
+
Core.warn_if_invalid_facts_marker(marker_found)
|
|
1869
|
+
|
|
1783
1870
|
log.info("Diff report artifact counts:")
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
added_artifacts = diff_artifacts.added + diff_artifacts.updated
|
|
1791
|
-
removed_artifacts = diff_artifacts.removed + diff_artifacts.replaced
|
|
1792
|
-
unchanged_artifacts = diff_artifacts.unchanged
|
|
1871
|
+
for name, bucket in buckets.items():
|
|
1872
|
+
log.info(f"{name.capitalize()}: {len(bucket)}")
|
|
1873
|
+
|
|
1874
|
+
added_artifacts = buckets["added"] + buckets["updated"]
|
|
1875
|
+
removed_artifacts = buckets["removed"] + buckets["replaced"]
|
|
1876
|
+
unchanged_artifacts = buckets["unchanged"]
|
|
1793
1877
|
|
|
1794
1878
|
added_packages: Dict[str, Package] = {}
|
|
1795
1879
|
removed_packages: Dict[str, Package] = {}
|
|
@@ -806,6 +806,90 @@ class Messages:
|
|
|
806
806
|
|
|
807
807
|
return gitlab_report
|
|
808
808
|
|
|
809
|
+
# A blank line terminates a CommonMark HTML block. When that happens inside
|
|
810
|
+
# the alerts table the closing tags that follow are no longer treated as
|
|
811
|
+
# markup, and because they are indented four or more spaces they render as a
|
|
812
|
+
# literal code block containing `</blockquote></details>` instead.
|
|
813
|
+
MAX_HTML_INDENT = 3
|
|
814
|
+
|
|
815
|
+
@staticmethod
|
|
816
|
+
def inline_html_text(value) -> str:
|
|
817
|
+
"""
|
|
818
|
+
Collapses API supplied text onto a single line.
|
|
819
|
+
|
|
820
|
+
Alert descriptions and suggestions are interpolated into the comment HTML,
|
|
821
|
+
so an embedded newline would otherwise be able to close the surrounding
|
|
822
|
+
HTML block early.
|
|
823
|
+
|
|
824
|
+
:param value: The value to flatten. ``None`` becomes an empty string.
|
|
825
|
+
:return: str - The value with all whitespace runs collapsed to a single space.
|
|
826
|
+
"""
|
|
827
|
+
if value is None:
|
|
828
|
+
return ""
|
|
829
|
+
return " ".join(str(value).split())
|
|
830
|
+
|
|
831
|
+
@staticmethod
|
|
832
|
+
def normalize_comment_html(comment: str) -> str:
|
|
833
|
+
"""
|
|
834
|
+
Makes generated comment markup safe for the CommonMark renderers used by
|
|
835
|
+
GitHub and GitLab.
|
|
836
|
+
|
|
837
|
+
Drops whitespace-only lines (an optional section that rendered as empty
|
|
838
|
+
leaves one behind) and caps indentation below the four spaces that would
|
|
839
|
+
start an indented code block. Intentional separators - lines that are
|
|
840
|
+
genuinely empty - are preserved so markdown blocks still break apart.
|
|
841
|
+
|
|
842
|
+
:param comment: str - The generated comment body.
|
|
843
|
+
:return: str - The comment body with unrenderable whitespace removed.
|
|
844
|
+
"""
|
|
845
|
+
lines = []
|
|
846
|
+
for line in comment.split("\n"):
|
|
847
|
+
if line and not line.strip():
|
|
848
|
+
continue
|
|
849
|
+
stripped = line.lstrip()
|
|
850
|
+
indent = min(len(line) - len(stripped), Messages.MAX_HTML_INDENT)
|
|
851
|
+
lines.append(" " * indent + stripped)
|
|
852
|
+
return "\n".join(lines)
|
|
853
|
+
|
|
854
|
+
@staticmethod
|
|
855
|
+
def security_comment_no_alerts_template(view_report_url: str = "") -> str:
|
|
856
|
+
"""
|
|
857
|
+
Generates the body used when there is nothing left to report.
|
|
858
|
+
|
|
859
|
+
Alerts raised on an early commit are frequently resolved later in the same
|
|
860
|
+
pull request. Rewriting the comment to this body keeps the Socket comment
|
|
861
|
+
in place - so a later commit that reintroduces an alert updates it rather
|
|
862
|
+
than posting a second comment - without leaving the "Caution" banner above
|
|
863
|
+
an empty alerts table.
|
|
864
|
+
|
|
865
|
+
:param view_report_url: str - Optional link to the full Socket report.
|
|
866
|
+
:return: str - The formatted Markdown/HTML string.
|
|
867
|
+
"""
|
|
868
|
+
lines = [
|
|
869
|
+
"<!-- socket-security-comment-actions -->",
|
|
870
|
+
"",
|
|
871
|
+
"> **✅ Socket Security** ",
|
|
872
|
+
"> No dependency alerts to report. Any alerts previously reported on this "
|
|
873
|
+
"pull request have been resolved or ignored.",
|
|
874
|
+
]
|
|
875
|
+
if view_report_url:
|
|
876
|
+
lines += ["", f"[View full report]({view_report_url})"]
|
|
877
|
+
return "\n".join(lines) + "\n"
|
|
878
|
+
|
|
879
|
+
@staticmethod
|
|
880
|
+
def get_view_report_url(diff: Diff) -> str:
|
|
881
|
+
"""
|
|
882
|
+
Resolves the report link for a diff, preferring the PR/MR diff view.
|
|
883
|
+
|
|
884
|
+
:param diff: Diff - Diff report to pull the URL from.
|
|
885
|
+
:return: str - The report URL, or an empty string when neither is set.
|
|
886
|
+
"""
|
|
887
|
+
if getattr(diff, "diff_url", None):
|
|
888
|
+
return diff.diff_url
|
|
889
|
+
if getattr(diff, "report_url", None):
|
|
890
|
+
return diff.report_url
|
|
891
|
+
return ""
|
|
892
|
+
|
|
809
893
|
@staticmethod
|
|
810
894
|
def security_comment_template(diff: Diff, config=None) -> str:
|
|
811
895
|
"""
|
|
@@ -819,7 +903,7 @@ class Messages:
|
|
|
819
903
|
# Group license policy violations by PURL (ecosystem/package@version)
|
|
820
904
|
license_groups = {}
|
|
821
905
|
security_alerts = []
|
|
822
|
-
|
|
906
|
+
|
|
823
907
|
for alert in diff.new_alerts:
|
|
824
908
|
if alert.type == "licenseSpdxDisj":
|
|
825
909
|
purl_key = f"{alert.pkg_type}/{alert.pkg_name}@{alert.pkg_version}"
|
|
@@ -829,6 +913,13 @@ class Messages:
|
|
|
829
913
|
else:
|
|
830
914
|
security_alerts.append(alert)
|
|
831
915
|
|
|
916
|
+
view_report_url = Messages.get_view_report_url(diff)
|
|
917
|
+
|
|
918
|
+
# Without this the caution banner would sit above a table with no rows,
|
|
919
|
+
# which is how a comment looks once every alert it raised is resolved.
|
|
920
|
+
if not security_alerts and not license_groups:
|
|
921
|
+
return Messages.security_comment_no_alerts_template(view_report_url)
|
|
922
|
+
|
|
832
923
|
# Start of the comment
|
|
833
924
|
comment = """<!-- socket-security-comment-actions -->
|
|
834
925
|
|
|
@@ -875,15 +966,15 @@ class Messages:
|
|
|
875
966
|
</td>
|
|
876
967
|
<td>
|
|
877
968
|
<details {details_open}>
|
|
878
|
-
<summary>{alert.pkg_name}@{alert.pkg_version} - {alert.title}</summary>
|
|
879
|
-
<p><strong>Note:</strong> {alert.description}</p>
|
|
969
|
+
<summary>{alert.pkg_name}@{alert.pkg_version} - {Messages.inline_html_text(alert.title)}</summary>
|
|
970
|
+
<p><strong>Note:</strong> {Messages.inline_html_text(alert.description)}</p>
|
|
880
971
|
<p><strong>Source:</strong> <a href="{manifest_url}">Manifest File</a></p>
|
|
881
972
|
<p>ℹ️ Read more on:
|
|
882
973
|
<a href="{alert.purl}">This package</a> |
|
|
883
974
|
<a href="{alert.url}">This alert</a> |
|
|
884
975
|
<a href="https://socket.dev/alerts/malware">What is known malware?</a></p>
|
|
885
976
|
<blockquote>
|
|
886
|
-
<p><em>Suggestion:</em> {alert.suggestion}</p>
|
|
977
|
+
<p><em>Suggestion:</em> {Messages.inline_html_text(alert.suggestion)}</p>
|
|
887
978
|
{ignore_html}
|
|
888
979
|
</blockquote>
|
|
889
980
|
</details>
|
|
@@ -917,7 +1008,7 @@ class Messages:
|
|
|
917
1008
|
<ul>
|
|
918
1009
|
"""
|
|
919
1010
|
for finding in license_findings:
|
|
920
|
-
comment += f" <li>{finding}</li>\n"
|
|
1011
|
+
comment += f" <li>{Messages.inline_html_text(finding)}</li>\n"
|
|
921
1012
|
|
|
922
1013
|
|
|
923
1014
|
# Generate proper manifest URL for license violations
|
|
@@ -944,13 +1035,6 @@ class Messages:
|
|
|
944
1035
|
"""
|
|
945
1036
|
|
|
946
1037
|
# Close table
|
|
947
|
-
# Use diff_url for PRs, report_url for non-PR scans
|
|
948
|
-
view_report_url = ""
|
|
949
|
-
if hasattr(diff, 'diff_url') and diff.diff_url:
|
|
950
|
-
view_report_url = diff.diff_url
|
|
951
|
-
elif hasattr(diff, 'report_url') and diff.report_url:
|
|
952
|
-
view_report_url = diff.report_url
|
|
953
|
-
|
|
954
1038
|
comment += f"""
|
|
955
1039
|
</tbody>
|
|
956
1040
|
</table>
|
|
@@ -959,7 +1043,7 @@ class Messages:
|
|
|
959
1043
|
[View full report]({view_report_url}?action=error%2Cwarn)
|
|
960
1044
|
"""
|
|
961
1045
|
|
|
962
|
-
return comment
|
|
1046
|
+
return Messages.normalize_comment_html(comment)
|
|
963
1047
|
|
|
964
1048
|
@staticmethod
|
|
965
1049
|
def get_severity_icon(severity: str) -> str:
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import re
|
|
2
3
|
|
|
3
4
|
from requests import Response
|
|
4
5
|
|
|
5
6
|
from socketsecurity.core import log
|
|
6
7
|
from socketsecurity.core.classes import Comment, Issue
|
|
8
|
+
from socketsecurity.core.messages import Messages
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class Comments:
|
|
12
|
+
VIEW_REPORT_PATTERN = re.compile(r"\[View full report\]\(([^)\s]+)\)")
|
|
13
|
+
|
|
10
14
|
@staticmethod
|
|
11
15
|
def process_response(response: Response) -> dict:
|
|
12
16
|
output = {}
|
|
@@ -84,6 +88,20 @@ class Comments:
|
|
|
84
88
|
is_heading_line = False
|
|
85
89
|
return is_heading_line
|
|
86
90
|
|
|
91
|
+
@staticmethod
|
|
92
|
+
def extract_report_url(body: str) -> str:
|
|
93
|
+
"""
|
|
94
|
+
Pulls the Socket report link out of an existing comment body so it can be
|
|
95
|
+
carried over when the comment is rewritten.
|
|
96
|
+
|
|
97
|
+
:param body: str - The existing comment body.
|
|
98
|
+
:return: str - The report URL without its query string, or "" if absent.
|
|
99
|
+
"""
|
|
100
|
+
match = Comments.VIEW_REPORT_PATTERN.search(body)
|
|
101
|
+
if not match:
|
|
102
|
+
return ""
|
|
103
|
+
return match.group(1).split("?", 1)[0]
|
|
104
|
+
|
|
87
105
|
@staticmethod
|
|
88
106
|
def process_security_comment(comment: Comment, comments) -> str:
|
|
89
107
|
ignore_all, ignore_commands = Comments.get_ignore_options(comments)
|
|
@@ -102,6 +120,7 @@ class Comments:
|
|
|
102
120
|
) -> str:
|
|
103
121
|
start = False
|
|
104
122
|
lines = []
|
|
123
|
+
kept_alert = False
|
|
105
124
|
for line in comment.body_list:
|
|
106
125
|
line = line.strip()
|
|
107
126
|
if "start-socket-alerts-table" in line:
|
|
@@ -114,17 +133,25 @@ class Comments:
|
|
|
114
133
|
ecosystem = ecosystem.lstrip("[")
|
|
115
134
|
pkg_name, pkg_version = details.split("@")
|
|
116
135
|
pkg_name = f"{ecosystem}/{pkg_name}"
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
136
|
+
# ignore_all has to be checked outside the loop: an ignore-all
|
|
137
|
+
# comment produces no ignore_commands, so a loop-internal check
|
|
138
|
+
# never runs and every row was kept.
|
|
139
|
+
ignore = ignore_all or any(
|
|
140
|
+
Comments.is_ignore(pkg_name, pkg_version, name, version)
|
|
141
|
+
for name, version in ignore_commands
|
|
142
|
+
)
|
|
121
143
|
if not ignore:
|
|
144
|
+
kept_alert = True
|
|
122
145
|
lines.append(line)
|
|
123
146
|
elif "end-socket-alerts-table" in line:
|
|
124
147
|
start = False
|
|
125
148
|
lines.append(line)
|
|
126
149
|
else:
|
|
127
150
|
lines.append(line)
|
|
151
|
+
if not kept_alert:
|
|
152
|
+
return Messages.security_comment_no_alerts_template(
|
|
153
|
+
Comments.extract_report_url("\n".join(comment.body_list))
|
|
154
|
+
)
|
|
128
155
|
return "\n".join(lines)
|
|
129
156
|
|
|
130
157
|
@staticmethod
|
|
@@ -145,17 +172,21 @@ class Comments:
|
|
|
145
172
|
"""
|
|
146
173
|
lines = []
|
|
147
174
|
ignore_section = False
|
|
175
|
+
kept_alert = False # Whether any alert row survived the ignore commands
|
|
148
176
|
pkg_name = pkg_version = "" # Track current package and version
|
|
149
177
|
|
|
150
178
|
# Loop through the comment lines
|
|
151
179
|
for line in comment.body_list:
|
|
152
|
-
line
|
|
180
|
+
# Match on the stripped line but keep the original, so the markup is
|
|
181
|
+
# rewritten with the same indentation it was generated with.
|
|
182
|
+
line = line.rstrip("\r")
|
|
183
|
+
stripped = line.strip()
|
|
153
184
|
|
|
154
185
|
# Detect the start of an alert section
|
|
155
|
-
if
|
|
186
|
+
if stripped.startswith("<!-- start-socket-alert-"):
|
|
156
187
|
# Extract package name and version from the comment
|
|
157
188
|
try:
|
|
158
|
-
start_marker =
|
|
189
|
+
start_marker = stripped[len("<!-- start-socket-alert-"):-4] # Strip the comment markers
|
|
159
190
|
pkg_name, pkg_version = start_marker.split("@") # Extract pkg_name and pkg_version
|
|
160
191
|
except ValueError:
|
|
161
192
|
pkg_name, pkg_version = "", ""
|
|
@@ -168,10 +199,11 @@ class Comments:
|
|
|
168
199
|
|
|
169
200
|
# If not ignored, include this start marker
|
|
170
201
|
if not ignore_section:
|
|
202
|
+
kept_alert = True
|
|
171
203
|
lines.append(line)
|
|
172
204
|
|
|
173
205
|
# Detect the end of an alert section
|
|
174
|
-
elif
|
|
206
|
+
elif stripped.startswith("<!-- end-socket-alert-"):
|
|
175
207
|
# Only include if we are not ignoring this section
|
|
176
208
|
if not ignore_section:
|
|
177
209
|
lines.append(line)
|
|
@@ -181,7 +213,14 @@ class Comments:
|
|
|
181
213
|
elif not ignore_section:
|
|
182
214
|
lines.append(line)
|
|
183
215
|
|
|
184
|
-
|
|
216
|
+
# Every row was ignored, so drop the table rather than leaving the caution
|
|
217
|
+
# banner sitting above an empty one.
|
|
218
|
+
if not kept_alert:
|
|
219
|
+
return Messages.security_comment_no_alerts_template(
|
|
220
|
+
Comments.extract_report_url("\n".join(comment.body_list))
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return Messages.normalize_comment_html("\n".join(lines))
|
|
185
224
|
|
|
186
225
|
@staticmethod
|
|
187
226
|
def extract_alert_details_from_row(row: str, ignore_all: bool, ignore_commands: list[tuple[str, str]]) -> tuple:
|
|
@@ -107,6 +107,27 @@ def get_api_request_timeout(config: CliConfig) -> int:
|
|
|
107
107
|
return config.timeout if config.timeout is not None else DEFAULT_API_TIMEOUT
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
def should_write_comment(disabled: bool, has_findings: bool, update_existing: bool) -> bool:
|
|
111
|
+
"""
|
|
112
|
+
Decides whether a Socket comment should be written for this run.
|
|
113
|
+
|
|
114
|
+
:param disabled: bool - The comment type is switched off by flag.
|
|
115
|
+
:param has_findings: bool - There is something to report this run.
|
|
116
|
+
:param update_existing: bool - A comment of this type is already on the PR.
|
|
117
|
+
:return: bool - True when the comment should be posted or updated.
|
|
118
|
+
"""
|
|
119
|
+
if disabled:
|
|
120
|
+
# The flag means the CLI does not manage this comment at all. Checking
|
|
121
|
+
# update_existing first only suppressed the very first post, so any
|
|
122
|
+
# comment already on the pull request kept being updated.
|
|
123
|
+
return False
|
|
124
|
+
if not has_findings:
|
|
125
|
+
# Nothing to report: refresh an existing comment so a resolved alerts
|
|
126
|
+
# table is cleared, but do not open a new one.
|
|
127
|
+
return update_existing
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
|
|
110
131
|
def build_socket_sdk(config: CliConfig) -> socketdev:
|
|
111
132
|
cli_user_agent_string = f"SocketPythonCLI/{config.version}"
|
|
112
133
|
return socketdev(
|
|
@@ -780,9 +801,6 @@ def main_code():
|
|
|
780
801
|
|
|
781
802
|
security_comment = Messages.security_comment_template(diff, config)
|
|
782
803
|
|
|
783
|
-
new_security_comment = True
|
|
784
|
-
new_overview_comment = True
|
|
785
|
-
|
|
786
804
|
update_old_security_comment = (
|
|
787
805
|
security_comment is None or
|
|
788
806
|
security_comment == "" or
|
|
@@ -795,20 +813,22 @@ def main_code():
|
|
|
795
813
|
(len(comments) != 0 and comments.get("overview") is not None)
|
|
796
814
|
)
|
|
797
815
|
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
816
|
+
new_security_comment = should_write_comment(
|
|
817
|
+
config.disable_security_issue,
|
|
818
|
+
len(diff.new_alerts) > 0,
|
|
819
|
+
update_old_security_comment,
|
|
820
|
+
)
|
|
821
|
+
if not new_security_comment:
|
|
822
|
+
log.debug("Security issue comment disabled, or no alerts and none to update")
|
|
823
|
+
|
|
805
824
|
# FIXME: diff.new_packages is never populated, neither is removed_packages
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
825
|
+
new_overview_comment = should_write_comment(
|
|
826
|
+
config.disable_overview,
|
|
827
|
+
len(diff.new_packages) > 0,
|
|
828
|
+
update_old_overview_comment,
|
|
829
|
+
)
|
|
830
|
+
if not new_overview_comment:
|
|
831
|
+
log.debug("Dependency Overview comment disabled, or no packages and none to update")
|
|
812
832
|
|
|
813
833
|
log.debug(f"Adding comments for {config.scm}")
|
|
814
834
|
scm.add_socket_comments(
|