socketsecurity 2.2.81__tar.gz → 2.2.85__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.2.81 → socketsecurity-2.2.85}/CHANGELOG.md +4 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/PKG-INFO +1 -1
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/docs/cli-reference.md +2 -1
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/pyproject.toml +1 -1
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/__init__.py +1 -1
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/config.py +47 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/cli_client.py +16 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/git_interface.py +6 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/messages.py +20 -9
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/scm/gitlab.py +42 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/scm_comments.py +5 -3
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/tools/reachability.py +16 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/socketcli.py +136 -8
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/unit/test_client.py +50 -1
- socketsecurity-2.2.85/tests/unit/test_disable_ignore.py +138 -0
- socketsecurity-2.2.85/tests/unit/test_ignore_telemetry_filtering.py +237 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/uv.lock +1 -1
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.github/CODEOWNERS +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.github/PULL_REQUEST_TEMPLATE/bug-fix.md +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.github/PULL_REQUEST_TEMPLATE/feature.md +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.github/PULL_REQUEST_TEMPLATE/improvement.md +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.github/workflows/docker-stable.yml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.github/workflows/e2e-test.yml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.github/workflows/pr-preview.yml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.github/workflows/python-tests.yml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.github/workflows/release.yml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.github/workflows/version-check.yml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.github/zizmor.yml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.gitignore +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.hooks/sync_version.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.pre-commit-config.yaml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/.python-version +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/Dockerfile +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/LICENSE +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/Makefile +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/README.md +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/docs/ci-cd.md +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/docs/development.md +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/docs/troubleshooting.md +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/examples/config/sarif-dashboard-parity.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/examples/config/sarif-dashboard-parity.toml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/examples/config/sarif-diff-ci-cd.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/examples/config/sarif-diff-ci-cd.toml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/examples/config/sarif-instance-detail.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/examples/config/sarif-instance-detail.toml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/instructions/gitlab-commit-status/uat.md +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/pytest.ini +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/scripts/build_container.sh +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/scripts/build_container_flexible.sh +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/scripts/deploy-test-docker.sh +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/scripts/deploy-test-pypi.sh +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/scripts/docker-entrypoint.sh +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/scripts/run.sh +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/session.md +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socket.yml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/__init__.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/alert_selection.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/classes.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/exceptions.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/helper/__init__.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/helper/socket_facts_loader.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/lazy_file_loader.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/logging.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/resource_utils.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/scm/__init__.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/scm/base.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/scm/client.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/scm/github.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/socket_config.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/core/utils.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/output.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/plugins/__init__.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/plugins/base.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/plugins/formatters/__init__.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/plugins/formatters/slack.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/plugins/jira.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/plugins/manager.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/plugins/slack.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/plugins/teams.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/socketsecurity/plugins/webhook.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/__init__.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/core/conftest.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/core/create_diff_input.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/core/test_diff_alerts.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/core/test_diff_generation.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/core/test_has_manifest_files.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/core/test_package_and_alerts.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/core/test_sdk_methods.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/core/test_supporting_methods.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/data/fullscans/create_response.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/data/fullscans/diff/stream_diff.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/data/fullscans/diff/stream_diff_full.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/data/fullscans/head_scan/metadata.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/data/fullscans/head_scan/stream_scan.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/data/fullscans/head_scan/stream_scan_full.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/data/fullscans/new_scan/metadata.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/data/fullscans/new_scan/stream_scan.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/data/repos/repo_info_error.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/data/repos/repo_info_no_head.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/data/repos/repo_info_success.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/data/settings/security-policy.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/e2e/fixtures/simple-npm/index.js +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/e2e/fixtures/simple-npm/package.json +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/e2e/fixtures/simple-pypi/requirements.txt +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/e2e/validate-gitlab.sh +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/e2e/validate-json.sh +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/e2e/validate-reachability.sh +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/e2e/validate-sarif.sh +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/e2e/validate-scan.sh +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/unit/__init__.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/unit/test_alert_selection.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/unit/test_cli_config.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/unit/test_config.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/unit/test_gitlab_auth.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/unit/test_gitlab_auth_fallback.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/unit/test_gitlab_commit_status.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/unit/test_gitlab_format.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/unit/test_output.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/tests/unit/test_slack_plugin.py +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/workflows/bitbucket-pipelines.yml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/workflows/buildkite.yml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/workflows/github-actions.yml +0 -0
- {socketsecurity-2.2.81 → socketsecurity-2.2.85}/workflows/gitlab-ci.yml +0 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.83
|
|
4
|
+
|
|
5
|
+
- Fixed branch detection in detached-HEAD CI checkouts. When `git name-rev --name-only HEAD` returned an output with a suffix operator (e.g. `remotes/origin/master~1`, `master^0`), the `~N`/`^N` was previously passed through as the branch name and rejected by the Socket API as an invalid Git ref. The suffix is now stripped before the prefix split, producing the bare branch name.
|
|
6
|
+
|
|
3
7
|
## 2.2.71
|
|
4
8
|
|
|
5
9
|
- Added `strace` to the Docker image for debugging purposes.
|
|
@@ -151,7 +151,7 @@ socketcli [-h] [--api-token API_TOKEN] [--repo REPO] [--workspace WORKSPACE] [--
|
|
|
151
151
|
[--excluded-ecosystems EXCLUDED_ECOSYSTEMS] [--default-branch] [--pending-head] [--generate-license] [--enable-debug]
|
|
152
152
|
[--enable-json] [--enable-sarif] [--sarif-file <path>] [--sarif-scope {diff,full}] [--sarif-grouping {instance,alert}] [--sarif-reachability {all,reachable,potentially,reachable-or-potentially}] [--enable-gitlab-security] [--gitlab-security-file <path>]
|
|
153
153
|
[--disable-overview] [--exclude-license-details] [--allow-unverified] [--disable-security-issue]
|
|
154
|
-
[--ignore-commit-files] [--disable-blocking] [--enable-diff] [--scm SCM] [--timeout TIMEOUT] [--include-module-folders]
|
|
154
|
+
[--ignore-commit-files] [--disable-blocking] [--disable-ignore] [--enable-diff] [--scm SCM] [--timeout TIMEOUT] [--include-module-folders]
|
|
155
155
|
[--reach] [--reach-version REACH_VERSION] [--reach-timeout REACH_ANALYSIS_TIMEOUT]
|
|
156
156
|
[--reach-memory-limit REACH_ANALYSIS_MEMORY_LIMIT] [--reach-ecosystems REACH_ECOSYSTEMS] [--reach-exclude-paths REACH_EXCLUDE_PATHS]
|
|
157
157
|
[--reach-min-severity {low,medium,high,critical}] [--reach-skip-cache] [--reach-disable-analytics] [--reach-output-file REACH_OUTPUT_FILE]
|
|
@@ -306,6 +306,7 @@ The CLI will automatically install `@coana-tech/cli` if not present. Use `--reac
|
|
|
306
306
|
|:-------------------------|:---------|:--------|:----------------------------------------------------------------------|
|
|
307
307
|
| `--ignore-commit-files` | False | False | Ignore commit files |
|
|
308
308
|
| `--disable-blocking` | False | False | Disable blocking mode |
|
|
309
|
+
| `--disable-ignore` | False | False | Disable support for `@SocketSecurity ignore` commands in PR comments. When set, alerts cannot be suppressed via comments and ignore instructions are hidden from comment output. |
|
|
309
310
|
| `--strict-blocking` | False | False | Fail on ANY security policy violations (blocking severity), not just new ones. Only works in diff mode. See [Strict Blocking Mode](#strict-blocking-mode) for details. |
|
|
310
311
|
| `--enable-diff` | False | False | Enable diff mode even when using `--integration api` (forces diff mode without SCM integration) |
|
|
311
312
|
| `--scm` | False | api | Source control management type |
|
|
@@ -91,6 +91,7 @@ class CliConfig:
|
|
|
91
91
|
files: str = None
|
|
92
92
|
ignore_commit_files: bool = False
|
|
93
93
|
disable_blocking: bool = False
|
|
94
|
+
disable_ignore: bool = False
|
|
94
95
|
strict_blocking: bool = False
|
|
95
96
|
integration_type: IntegrationType = "api"
|
|
96
97
|
integration_org_slug: Optional[str] = None
|
|
@@ -130,6 +131,10 @@ class CliConfig:
|
|
|
130
131
|
reach_additional_params: Optional[List[str]] = None
|
|
131
132
|
only_facts_file: bool = False
|
|
132
133
|
reach_use_only_pregenerated_sboms: bool = False
|
|
134
|
+
reach_continue_on_analysis_errors: bool = False
|
|
135
|
+
reach_continue_on_install_errors: bool = False
|
|
136
|
+
reach_continue_on_missing_lock_files: bool = False
|
|
137
|
+
reach_continue_on_no_source_files: bool = False
|
|
133
138
|
max_purl_batch_size: int = 5000
|
|
134
139
|
enable_commit_status: bool = False
|
|
135
140
|
config_file: Optional[str] = None
|
|
@@ -201,6 +206,7 @@ class CliConfig:
|
|
|
201
206
|
'files': args.files,
|
|
202
207
|
'ignore_commit_files': args.ignore_commit_files,
|
|
203
208
|
'disable_blocking': args.disable_blocking,
|
|
209
|
+
'disable_ignore': args.disable_ignore,
|
|
204
210
|
'strict_blocking': args.strict_blocking,
|
|
205
211
|
'integration_type': args.integration,
|
|
206
212
|
'pending_head': args.pending_head,
|
|
@@ -234,6 +240,10 @@ class CliConfig:
|
|
|
234
240
|
'reach_additional_params': args.reach_additional_params,
|
|
235
241
|
'only_facts_file': args.only_facts_file,
|
|
236
242
|
'reach_use_only_pregenerated_sboms': args.reach_use_only_pregenerated_sboms,
|
|
243
|
+
'reach_continue_on_analysis_errors': args.reach_continue_on_analysis_errors,
|
|
244
|
+
'reach_continue_on_install_errors': args.reach_continue_on_install_errors,
|
|
245
|
+
'reach_continue_on_missing_lock_files': args.reach_continue_on_missing_lock_files,
|
|
246
|
+
'reach_continue_on_no_source_files': args.reach_continue_on_no_source_files,
|
|
237
247
|
'max_purl_batch_size': args.max_purl_batch_size,
|
|
238
248
|
'enable_commit_status': args.enable_commit_status,
|
|
239
249
|
'config_file': args.config_file,
|
|
@@ -693,6 +703,19 @@ def create_argument_parser() -> argparse.ArgumentParser:
|
|
|
693
703
|
action="store_true",
|
|
694
704
|
help=argparse.SUPPRESS
|
|
695
705
|
)
|
|
706
|
+
advanced_group.add_argument(
|
|
707
|
+
"--disable-ignore",
|
|
708
|
+
dest="disable_ignore",
|
|
709
|
+
action="store_true",
|
|
710
|
+
help="Disable support for @SocketSecurity ignore commands in PR comments. "
|
|
711
|
+
"Alerts cannot be suppressed via comments when this flag is set."
|
|
712
|
+
)
|
|
713
|
+
advanced_group.add_argument(
|
|
714
|
+
"--disable_ignore",
|
|
715
|
+
dest="disable_ignore",
|
|
716
|
+
action="store_true",
|
|
717
|
+
help=argparse.SUPPRESS
|
|
718
|
+
)
|
|
696
719
|
advanced_group.add_argument(
|
|
697
720
|
"--strict-blocking",
|
|
698
721
|
dest="strict_blocking",
|
|
@@ -846,6 +869,30 @@ def create_argument_parser() -> argparse.ArgumentParser:
|
|
|
846
869
|
action="store_true",
|
|
847
870
|
help="When using this option, the scan is created based only on pre-generated CDX and SPDX files in your project. (requires --reach)"
|
|
848
871
|
)
|
|
872
|
+
reachability_group.add_argument(
|
|
873
|
+
"--reach-continue-on-analysis-errors",
|
|
874
|
+
dest="reach_continue_on_analysis_errors",
|
|
875
|
+
action="store_true",
|
|
876
|
+
help=argparse.SUPPRESS
|
|
877
|
+
)
|
|
878
|
+
reachability_group.add_argument(
|
|
879
|
+
"--reach-continue-on-install-errors",
|
|
880
|
+
dest="reach_continue_on_install_errors",
|
|
881
|
+
action="store_true",
|
|
882
|
+
help=argparse.SUPPRESS
|
|
883
|
+
)
|
|
884
|
+
reachability_group.add_argument(
|
|
885
|
+
"--reach-continue-on-missing-lock-files",
|
|
886
|
+
dest="reach_continue_on_missing_lock_files",
|
|
887
|
+
action="store_true",
|
|
888
|
+
help=argparse.SUPPRESS
|
|
889
|
+
)
|
|
890
|
+
reachability_group.add_argument(
|
|
891
|
+
"--reach-continue-on-no-source-files",
|
|
892
|
+
dest="reach_continue_on_no_source_files",
|
|
893
|
+
action="store_true",
|
|
894
|
+
help=argparse.SUPPRESS
|
|
895
|
+
)
|
|
849
896
|
|
|
850
897
|
parser.add_argument(
|
|
851
898
|
'--version',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import base64
|
|
2
|
+
import json
|
|
2
3
|
import logging
|
|
3
4
|
from typing import Dict, List, Optional, Union
|
|
4
5
|
|
|
@@ -55,3 +56,18 @@ class CliClient:
|
|
|
55
56
|
except requests.exceptions.RequestException as e:
|
|
56
57
|
logger.error(f"API request failed: {str(e)}")
|
|
57
58
|
raise APIFailure(f"Request failed: {str(e)}")
|
|
59
|
+
|
|
60
|
+
def post_telemetry_events(self, org_slug: str, events: List[Dict]) -> None:
|
|
61
|
+
"""Post telemetry events one at a time to the v0 telemetry API. Fire-and-forget — logs errors but never raises."""
|
|
62
|
+
logger.debug(f"Sending {len(events)} telemetry event(s) to v0/orgs/{org_slug}/telemetry")
|
|
63
|
+
for i, event in enumerate(events):
|
|
64
|
+
try:
|
|
65
|
+
logger.debug(f"Telemetry event {i+1}/{len(events)}: {json.dumps(event)}")
|
|
66
|
+
resp = self.request(
|
|
67
|
+
path=f"orgs/{org_slug}/telemetry",
|
|
68
|
+
method="POST",
|
|
69
|
+
payload=json.dumps(event),
|
|
70
|
+
)
|
|
71
|
+
logger.debug(f"Telemetry event {i+1}/{len(events)} sent: status={resp.status_code}")
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.warning(f"Failed to send telemetry event {i+1}/{len(events)}: {e}")
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import re
|
|
1
2
|
import urllib.parse
|
|
2
3
|
import os
|
|
3
4
|
|
|
@@ -108,6 +109,11 @@ class Git:
|
|
|
108
109
|
# Try git name-rev first (most reliable for detached HEAD)
|
|
109
110
|
result = self.repo.git.name_rev('--name-only', 'HEAD')
|
|
110
111
|
if result and result != 'undefined':
|
|
112
|
+
# Strip name-rev suffix operators (~N, ^N, or combinations
|
|
113
|
+
# like master~3^2). These characters are forbidden in git
|
|
114
|
+
# ref names, so cutting at the first occurrence can never
|
|
115
|
+
# truncate a real branch name.
|
|
116
|
+
result = re.split(r'[~^]', result, maxsplit=1)[0]
|
|
111
117
|
# Clean up the result (remove any prefixes like 'remotes/origin/')
|
|
112
118
|
git_detected_branch = result.split('/')[-1]
|
|
113
119
|
log.debug(f"Branch detected from git name-rev: {git_detected_branch}")
|
|
@@ -850,6 +850,8 @@ class Messages:
|
|
|
850
850
|
<tbody>
|
|
851
851
|
"""
|
|
852
852
|
|
|
853
|
+
show_ignore = not (config and getattr(config, 'disable_ignore', False))
|
|
854
|
+
|
|
853
855
|
# Loop through security alerts (non-license), dynamically generating rows
|
|
854
856
|
for alert in security_alerts:
|
|
855
857
|
severity_icon = Messages.get_severity_icon(alert.severity)
|
|
@@ -858,6 +860,12 @@ class Messages:
|
|
|
858
860
|
# Generate proper manifest URL
|
|
859
861
|
manifest_url = Messages.get_manifest_file_url(diff, alert.manifests, config)
|
|
860
862
|
# Generate a table row for each alert
|
|
863
|
+
ignore_html = (
|
|
864
|
+
f"<p><em>Mark as acceptable risk:</em> To ignore this alert only in this pull request, reply with:<br/>"
|
|
865
|
+
f"<code>@SocketSecurity ignore {alert.pkg_name}@{alert.pkg_version}</code><br/>"
|
|
866
|
+
f"Or ignore all future alerts with:<br/>"
|
|
867
|
+
f"<code>@SocketSecurity ignore-all</code></p>"
|
|
868
|
+
) if show_ignore else ""
|
|
861
869
|
comment += f"""
|
|
862
870
|
<!-- start-socket-alert-{alert.pkg_name}@{alert.pkg_version} -->
|
|
863
871
|
<tr>
|
|
@@ -870,16 +878,13 @@ class Messages:
|
|
|
870
878
|
<summary>{alert.pkg_name}@{alert.pkg_version} - {alert.title}</summary>
|
|
871
879
|
<p><strong>Note:</strong> {alert.description}</p>
|
|
872
880
|
<p><strong>Source:</strong> <a href="{manifest_url}">Manifest File</a></p>
|
|
873
|
-
<p>ℹ️ Read more on:
|
|
874
|
-
<a href="{alert.purl}">This package</a> |
|
|
875
|
-
<a href="{alert.url}">This alert</a> |
|
|
881
|
+
<p>ℹ️ Read more on:
|
|
882
|
+
<a href="{alert.purl}">This package</a> |
|
|
883
|
+
<a href="{alert.url}">This alert</a> |
|
|
876
884
|
<a href="https://socket.dev/alerts/malware">What is known malware?</a></p>
|
|
877
885
|
<blockquote>
|
|
878
886
|
<p><em>Suggestion:</em> {alert.suggestion}</p>
|
|
879
|
-
|
|
880
|
-
<code>@SocketSecurity ignore {alert.pkg_name}@{alert.pkg_version}</code><br/>
|
|
881
|
-
Or ignore all future alerts with:<br/>
|
|
882
|
-
<code>@SocketSecurity ignore-all</code></p>
|
|
887
|
+
{ignore_html}
|
|
883
888
|
</blockquote>
|
|
884
889
|
</details>
|
|
885
890
|
</td>
|
|
@@ -917,14 +922,20 @@ class Messages:
|
|
|
917
922
|
|
|
918
923
|
# Generate proper manifest URL for license violations
|
|
919
924
|
license_manifest_url = Messages.get_manifest_file_url(diff, first_alert.manifests, config)
|
|
920
|
-
|
|
925
|
+
|
|
926
|
+
license_ignore_html = (
|
|
927
|
+
f"<p><em>Mark the package as acceptable risk:</em> To ignore this alert only in this pull request, reply with the comment "
|
|
928
|
+
f"<code>@SocketSecurity ignore {first_alert.pkg_name}@{first_alert.pkg_version}</code>. "
|
|
929
|
+
f"You can also ignore all packages with <code>@SocketSecurity ignore-all</code>. "
|
|
930
|
+
f"To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.</p>"
|
|
931
|
+
) if show_ignore else ""
|
|
921
932
|
comment += f""" </ul>
|
|
922
933
|
<p><strong>From:</strong> <a href="{license_manifest_url}">Manifest File</a></p>
|
|
923
934
|
<p>ℹ️ Read more on: <a href="{first_alert.purl}">This package</a> | <a href="https://socket.dev/alerts/license">What is a license policy violation?</a></p>
|
|
924
935
|
<blockquote>
|
|
925
936
|
<p><em>Next steps:</em> Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at <strong>support@socket.dev</strong>.</p>
|
|
926
937
|
<p><em>Suggestion:</em> Find a package that does not violate your license policy or adjust your policy to allow this package's license.</p>
|
|
927
|
-
|
|
938
|
+
{license_ignore_html}
|
|
928
939
|
</blockquote>
|
|
929
940
|
</details>
|
|
930
941
|
</td>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import os
|
|
2
3
|
import sys
|
|
3
4
|
from dataclasses import dataclass
|
|
@@ -219,6 +220,24 @@ class Gitlab:
|
|
|
219
220
|
base_url=self.config.api_url
|
|
220
221
|
)
|
|
221
222
|
|
|
223
|
+
def has_thumbsup_reaction(self, comment_id: int) -> bool:
|
|
224
|
+
"""Best-effort check for 'thumbsup' award emoji on a MR note."""
|
|
225
|
+
if not self.config.mr_project_id or not self.config.mr_iid:
|
|
226
|
+
return False
|
|
227
|
+
path = f"projects/{self.config.mr_project_id}/merge_requests/{self.config.mr_iid}/notes/{comment_id}/award_emoji"
|
|
228
|
+
try:
|
|
229
|
+
response = self._request_with_fallback(
|
|
230
|
+
path=path,
|
|
231
|
+
headers=self.config.headers,
|
|
232
|
+
base_url=self.config.api_url
|
|
233
|
+
)
|
|
234
|
+
for emoji in response.json():
|
|
235
|
+
if emoji.get("name") == "thumbsup":
|
|
236
|
+
return True
|
|
237
|
+
except Exception as e:
|
|
238
|
+
log.debug(f"Could not check award emoji for note {comment_id} (best effort): {e}")
|
|
239
|
+
return False
|
|
240
|
+
|
|
222
241
|
def get_comments_for_pr(self) -> dict:
|
|
223
242
|
log.debug(f"Getting Gitlab comments for Repo {self.config.repository} for PR {self.config.mr_iid}")
|
|
224
243
|
path = f"projects/{self.config.mr_project_id}/merge_requests/{self.config.mr_iid}/notes"
|
|
@@ -326,9 +345,32 @@ class Gitlab:
|
|
|
326
345
|
except Exception as e:
|
|
327
346
|
log.error(f"Failed to set commit status: {e}")
|
|
328
347
|
|
|
348
|
+
def post_thumbsup_reaction(self, comment_id: int) -> None:
|
|
349
|
+
"""Best-effort: add 'thumbsup' award emoji to a MR note."""
|
|
350
|
+
if not self.config.mr_project_id or not self.config.mr_iid:
|
|
351
|
+
return
|
|
352
|
+
path = f"projects/{self.config.mr_project_id}/merge_requests/{self.config.mr_iid}/notes/{comment_id}/award_emoji"
|
|
353
|
+
try:
|
|
354
|
+
headers = {**self.config.headers, "Content-Type": "application/json"}
|
|
355
|
+
self._request_with_fallback(
|
|
356
|
+
path=path,
|
|
357
|
+
payload=json.dumps({"name": "thumbsup"}),
|
|
358
|
+
method="POST",
|
|
359
|
+
headers=headers,
|
|
360
|
+
base_url=self.config.api_url
|
|
361
|
+
)
|
|
362
|
+
except Exception as e:
|
|
363
|
+
log.debug(f"Could not add thumbsup emoji to note {comment_id} (best effort): {e}")
|
|
364
|
+
|
|
365
|
+
def handle_ignore_reactions(self, comments: dict) -> None:
|
|
366
|
+
for comment in comments.get("ignore", []):
|
|
367
|
+
if "SocketSecurity ignore" in comment.body and not self.has_thumbsup_reaction(comment.id):
|
|
368
|
+
self.post_thumbsup_reaction(comment.id)
|
|
369
|
+
|
|
329
370
|
def remove_comment_alerts(self, comments: dict):
|
|
330
371
|
security_alert = comments.get("security")
|
|
331
372
|
if security_alert is not None:
|
|
332
373
|
# Type narrowing: after None check, mypy knows this is Comment
|
|
333
374
|
new_body = Comments.process_security_comment(security_alert, comments)
|
|
375
|
+
self.handle_ignore_reactions(comments)
|
|
334
376
|
self.update_comment(new_body, str(security_alert.id))
|
|
@@ -51,11 +51,13 @@ class Comments:
|
|
|
51
51
|
for comment in comments["ignore"]:
|
|
52
52
|
comment: Comment
|
|
53
53
|
first_line = comment.body_list[0]
|
|
54
|
-
if not ignore_all and "
|
|
54
|
+
if not ignore_all and "socketsecurity ignore" in first_line.lower():
|
|
55
55
|
try:
|
|
56
56
|
first_line = first_line.lstrip("@")
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
# Case-insensitive split: find "SocketSecurity " regardless of casing
|
|
58
|
+
lower_line = first_line.lower()
|
|
59
|
+
split_idx = lower_line.index("socketsecurity ") + len("socketsecurity ")
|
|
60
|
+
command = first_line[split_idx:].strip()
|
|
59
61
|
if command == "ignore-all":
|
|
60
62
|
ignore_all = True
|
|
61
63
|
else:
|
|
@@ -104,6 +104,10 @@ class ReachabilityAnalyzer:
|
|
|
104
104
|
allow_unverified: bool = False,
|
|
105
105
|
enable_debug: bool = False,
|
|
106
106
|
use_only_pregenerated_sboms: bool = False,
|
|
107
|
+
continue_on_analysis_errors: bool = False,
|
|
108
|
+
continue_on_install_errors: bool = False,
|
|
109
|
+
continue_on_missing_lock_files: bool = False,
|
|
110
|
+
continue_on_no_source_files: bool = False,
|
|
107
111
|
) -> Dict[str, Any]:
|
|
108
112
|
"""
|
|
109
113
|
Run reachability analysis.
|
|
@@ -196,6 +200,18 @@ class ReachabilityAnalyzer:
|
|
|
196
200
|
if use_only_pregenerated_sboms:
|
|
197
201
|
cmd.append("--use-only-pregenerated-sboms")
|
|
198
202
|
|
|
203
|
+
if continue_on_analysis_errors:
|
|
204
|
+
cmd.append("--reach-continue-on-analysis-errors")
|
|
205
|
+
|
|
206
|
+
if continue_on_install_errors:
|
|
207
|
+
cmd.append("--reach-continue-on-install-errors")
|
|
208
|
+
|
|
209
|
+
if continue_on_missing_lock_files:
|
|
210
|
+
cmd.append("--reach-continue-on-missing-lock-files")
|
|
211
|
+
|
|
212
|
+
if continue_on_no_source_files:
|
|
213
|
+
cmd.append("--reach-continue-on-no-source-files")
|
|
214
|
+
|
|
199
215
|
# Add any additional parameters provided by the user
|
|
200
216
|
if additional_params:
|
|
201
217
|
cmd.extend(additional_params)
|
|
@@ -4,6 +4,8 @@ import sys
|
|
|
4
4
|
import traceback
|
|
5
5
|
import shutil
|
|
6
6
|
import warnings
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
from uuid import uuid4
|
|
7
9
|
|
|
8
10
|
from dotenv import load_dotenv
|
|
9
11
|
from git import InvalidGitRepositoryError, NoSuchPathError
|
|
@@ -301,7 +303,11 @@ def main_code():
|
|
|
301
303
|
additional_params=config.reach_additional_params,
|
|
302
304
|
allow_unverified=config.allow_unverified,
|
|
303
305
|
enable_debug=config.enable_debug,
|
|
304
|
-
use_only_pregenerated_sboms=config.reach_use_only_pregenerated_sboms
|
|
306
|
+
use_only_pregenerated_sboms=config.reach_use_only_pregenerated_sboms,
|
|
307
|
+
continue_on_analysis_errors=config.reach_continue_on_analysis_errors,
|
|
308
|
+
continue_on_install_errors=config.reach_continue_on_install_errors,
|
|
309
|
+
continue_on_missing_lock_files=config.reach_continue_on_missing_lock_files,
|
|
310
|
+
continue_on_no_source_files=config.reach_continue_on_no_source_files,
|
|
305
311
|
)
|
|
306
312
|
|
|
307
313
|
log.info(f"Reachability analysis completed successfully")
|
|
@@ -478,6 +484,17 @@ def main_code():
|
|
|
478
484
|
|
|
479
485
|
# Handle SCM-specific flows
|
|
480
486
|
log.debug(f"Flow decision: scm={scm is not None}, force_diff_mode={force_diff_mode}, force_api_mode={force_api_mode}, enable_diff={config.enable_diff}")
|
|
487
|
+
|
|
488
|
+
def _is_unprocessed(c):
|
|
489
|
+
"""Check if an ignore comment has not yet been marked with '+1' reaction.
|
|
490
|
+
For GitHub, reactions['+1'] is already in the comment response (no extra call).
|
|
491
|
+
For GitLab, has_thumbsup_reaction() makes a lazy API call per comment."""
|
|
492
|
+
if getattr(c, "reactions", {}).get("+1"):
|
|
493
|
+
return False
|
|
494
|
+
if hasattr(scm, "has_thumbsup_reaction") and scm.has_thumbsup_reaction(c.id):
|
|
495
|
+
return False
|
|
496
|
+
return True
|
|
497
|
+
|
|
481
498
|
if scm is not None and scm.check_event_type() == "comment":
|
|
482
499
|
# FIXME: This entire flow should be a separate command called "filter_ignored_alerts_in_comments"
|
|
483
500
|
# It's not related to scanning or diff generation - it just:
|
|
@@ -486,10 +503,51 @@ def main_code():
|
|
|
486
503
|
# 3. Updates the comment to remove ignored alerts
|
|
487
504
|
# This is completely separate from the main scanning functionality
|
|
488
505
|
log.info("Comment initiated flow")
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
506
|
+
|
|
507
|
+
if not config.disable_ignore:
|
|
508
|
+
comments = scm.get_comments_for_pr()
|
|
509
|
+
|
|
510
|
+
# Emit telemetry for ignore comments before +1 reaction is added.
|
|
511
|
+
# The +1 reaction (added by remove_comment_alerts) serves as the "processed" marker.
|
|
512
|
+
if "ignore" in comments:
|
|
513
|
+
unprocessed = [c for c in comments["ignore"] if _is_unprocessed(c)]
|
|
514
|
+
if unprocessed:
|
|
515
|
+
try:
|
|
516
|
+
events = []
|
|
517
|
+
for c in unprocessed:
|
|
518
|
+
single = {"ignore": [c]}
|
|
519
|
+
ignore_all, ignore_commands = Comments.get_ignore_options(single)
|
|
520
|
+
user = getattr(c, "user", None) or getattr(c, "author", None) or {}
|
|
521
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
522
|
+
shared_fields = {
|
|
523
|
+
"event_kind": "user-action",
|
|
524
|
+
"client_action": "ignore",
|
|
525
|
+
"alert_action": "error",
|
|
526
|
+
"event_sender_created_at": now,
|
|
527
|
+
"vcs_provider": integration_type,
|
|
528
|
+
"owner": config.repo.split("/")[0] if "/" in config.repo else "",
|
|
529
|
+
"repo": config.repo,
|
|
530
|
+
"pr_number": pr_number,
|
|
531
|
+
"ignore_all": ignore_all,
|
|
532
|
+
"sender_name": user.get("login") or user.get("username", ""),
|
|
533
|
+
"sender_id": str(user.get("id", "")),
|
|
534
|
+
}
|
|
535
|
+
if ignore_commands:
|
|
536
|
+
for name, version in ignore_commands:
|
|
537
|
+
events.append({**shared_fields, "event_id": str(uuid4()), "artifact_input": f"{name}@{version}"})
|
|
538
|
+
elif ignore_all:
|
|
539
|
+
events.append({**shared_fields, "event_id": str(uuid4())})
|
|
540
|
+
|
|
541
|
+
if events:
|
|
542
|
+
log.debug(f"Ignore telemetry: {len(events)} events to send")
|
|
543
|
+
client.post_telemetry_events(org_slug, events)
|
|
544
|
+
except Exception as e:
|
|
545
|
+
log.warning(f"Failed to send ignore telemetry: {e}")
|
|
546
|
+
|
|
547
|
+
log.debug("Removing comment alerts")
|
|
548
|
+
scm.remove_comment_alerts(comments)
|
|
549
|
+
else:
|
|
550
|
+
log.info("Ignore commands disabled (--disable-ignore), skipping comment processing")
|
|
493
551
|
|
|
494
552
|
elif scm is not None and scm.check_event_type() != "comment" and not force_api_mode:
|
|
495
553
|
log.info("Push initiated flow")
|
|
@@ -497,10 +555,80 @@ def main_code():
|
|
|
497
555
|
log.info("Starting comment logic for PR/MR event")
|
|
498
556
|
diff = core.create_new_diff(scan_paths, params, no_change=should_skip_scan, save_files_list_path=config.save_submitted_files_list, save_manifest_tar_path=config.save_manifest_tar, base_paths=base_paths, explicit_files=sbom_files_to_submit)
|
|
499
557
|
comments = scm.get_comments_for_pr()
|
|
500
|
-
|
|
501
|
-
|
|
558
|
+
|
|
502
559
|
# FIXME: this overwrites diff.new_alerts, which was previously populated by Core.create_issue_alerts
|
|
503
|
-
|
|
560
|
+
if not config.disable_ignore:
|
|
561
|
+
log.debug("Removing comment alerts")
|
|
562
|
+
alerts_before = list(diff.new_alerts)
|
|
563
|
+
diff.new_alerts = Comments.remove_alerts(comments, diff.new_alerts)
|
|
564
|
+
|
|
565
|
+
ignored_alerts = [a for a in alerts_before if a not in diff.new_alerts]
|
|
566
|
+
# Emit telemetry per-comment so each event carries the comment author.
|
|
567
|
+
unprocessed_ignore = [
|
|
568
|
+
c for c in comments.get("ignore", [])
|
|
569
|
+
if _is_unprocessed(c)
|
|
570
|
+
]
|
|
571
|
+
if ignored_alerts and unprocessed_ignore:
|
|
572
|
+
try:
|
|
573
|
+
events = []
|
|
574
|
+
now = datetime.now(timezone.utc).isoformat()
|
|
575
|
+
for c in unprocessed_ignore:
|
|
576
|
+
single = {"ignore": [c]}
|
|
577
|
+
c_ignore_all, c_ignore_commands = Comments.get_ignore_options(single)
|
|
578
|
+
user = getattr(c, "user", None) or getattr(c, "author", None) or {}
|
|
579
|
+
sender_name = user.get("login") or user.get("username", "")
|
|
580
|
+
sender_id = str(user.get("id", ""))
|
|
581
|
+
|
|
582
|
+
# Match this comment's targets to the actual ignored alerts
|
|
583
|
+
matched_alerts = []
|
|
584
|
+
if c_ignore_all:
|
|
585
|
+
matched_alerts = ignored_alerts
|
|
586
|
+
else:
|
|
587
|
+
for alert in ignored_alerts:
|
|
588
|
+
full_name = f"{alert.pkg_type}/{alert.pkg_name}"
|
|
589
|
+
purl = (full_name, alert.pkg_version)
|
|
590
|
+
purl_star = (full_name, "*")
|
|
591
|
+
if purl in c_ignore_commands or purl_star in c_ignore_commands:
|
|
592
|
+
matched_alerts.append(alert)
|
|
593
|
+
|
|
594
|
+
shared_fields = {
|
|
595
|
+
"event_kind": "user-action",
|
|
596
|
+
"client_action": "ignore",
|
|
597
|
+
"event_sender_created_at": now,
|
|
598
|
+
"vcs_provider": integration_type,
|
|
599
|
+
"owner": config.repo.split("/")[0] if "/" in config.repo else "",
|
|
600
|
+
"repo": config.repo,
|
|
601
|
+
"pr_number": pr_number,
|
|
602
|
+
"ignore_all": c_ignore_all,
|
|
603
|
+
"sender_name": sender_name,
|
|
604
|
+
"sender_id": sender_id,
|
|
605
|
+
}
|
|
606
|
+
if matched_alerts:
|
|
607
|
+
for alert in matched_alerts:
|
|
608
|
+
# Derive alert_action from the alert's resolved action flags
|
|
609
|
+
if getattr(alert, "error", False):
|
|
610
|
+
alert_action = "error"
|
|
611
|
+
elif getattr(alert, "warn", False):
|
|
612
|
+
alert_action = "warn"
|
|
613
|
+
elif getattr(alert, "monitor", False):
|
|
614
|
+
alert_action = "monitor"
|
|
615
|
+
else:
|
|
616
|
+
alert_action = "error"
|
|
617
|
+
events.append({**shared_fields, "alert_action": alert_action, "event_id": str(uuid4()), "artifact_purl": alert.purl})
|
|
618
|
+
elif c_ignore_all:
|
|
619
|
+
events.append({**shared_fields, "event_id": str(uuid4())})
|
|
620
|
+
|
|
621
|
+
if events:
|
|
622
|
+
client.post_telemetry_events(org_slug, events)
|
|
623
|
+
|
|
624
|
+
# Mark ignore comments as processed with +1 reaction
|
|
625
|
+
if hasattr(scm, "handle_ignore_reactions"):
|
|
626
|
+
scm.handle_ignore_reactions(comments)
|
|
627
|
+
except Exception as e:
|
|
628
|
+
log.warning(f"Failed to send ignore telemetry: {e}")
|
|
629
|
+
else:
|
|
630
|
+
log.info("Ignore commands disabled (--disable-ignore), all alerts will be reported")
|
|
631
|
+
|
|
504
632
|
log.debug("Creating Dependency Overview Comment")
|
|
505
633
|
|
|
506
634
|
overview_comment = Messages.dependency_overview_template(diff)
|
|
@@ -122,4 +122,53 @@ def test_request_with_payload(client):
|
|
|
122
122
|
|
|
123
123
|
args, kwargs = mock_request.call_args
|
|
124
124
|
assert kwargs['method'] == "POST"
|
|
125
|
-
assert kwargs['data'] == payload
|
|
125
|
+
assert kwargs['data'] == payload
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_post_telemetry_events_sends_individually(client):
|
|
129
|
+
"""Test that telemetry events are posted one at a time to v0 API"""
|
|
130
|
+
import json
|
|
131
|
+
|
|
132
|
+
events = [
|
|
133
|
+
{"event_kind": "user-action", "client_action": "ignore_alerts", "artifact_purl": "pkg:npm/foo@1.0.0"},
|
|
134
|
+
{"event_kind": "user-action", "client_action": "ignore_alerts", "artifact_purl": "pkg:npm/bar@2.0.0"},
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
with patch('requests.request') as mock_request:
|
|
138
|
+
mock_response = Mock()
|
|
139
|
+
mock_response.status_code = 201
|
|
140
|
+
mock_request.return_value = mock_response
|
|
141
|
+
|
|
142
|
+
client.post_telemetry_events("test-org", events)
|
|
143
|
+
|
|
144
|
+
assert mock_request.call_count == 2
|
|
145
|
+
|
|
146
|
+
first_call = mock_request.call_args_list[0]
|
|
147
|
+
assert first_call.kwargs['url'] == "https://api.socket.dev/v0/orgs/test-org/telemetry"
|
|
148
|
+
assert first_call.kwargs['method'] == "POST"
|
|
149
|
+
assert first_call.kwargs['data'] == json.dumps(events[0])
|
|
150
|
+
|
|
151
|
+
second_call = mock_request.call_args_list[1]
|
|
152
|
+
assert second_call.kwargs['data'] == json.dumps(events[1])
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_post_telemetry_events_continues_on_failure(client):
|
|
156
|
+
"""Test that a failed event does not prevent subsequent events from being sent"""
|
|
157
|
+
import json
|
|
158
|
+
|
|
159
|
+
events = [
|
|
160
|
+
{"event_kind": "user-action", "artifact_purl": "pkg:npm/foo@1.0.0"},
|
|
161
|
+
{"event_kind": "user-action", "artifact_purl": "pkg:npm/bar@2.0.0"},
|
|
162
|
+
]
|
|
163
|
+
|
|
164
|
+
with patch('requests.request') as mock_request:
|
|
165
|
+
mock_response = Mock()
|
|
166
|
+
mock_response.status_code = 201
|
|
167
|
+
mock_request.side_effect = [
|
|
168
|
+
requests.exceptions.ConnectionError("timeout"),
|
|
169
|
+
mock_response,
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
client.post_telemetry_events("test-org", events)
|
|
173
|
+
|
|
174
|
+
assert mock_request.call_count == 2
|