|
4 | 4 | import sys |
5 | 5 | import traceback |
6 | 6 | from datetime import datetime, timezone |
| 7 | +from typing import List, Optional, Tuple |
7 | 8 | from uuid import uuid4 |
8 | 9 |
|
9 | 10 | from dotenv import load_dotenv |
|
18 | 19 | from socketsecurity.core.git_interface import Git |
19 | 20 | from socketsecurity.core.logging import initialize_logging, set_debug_mode |
20 | 21 | from socketsecurity.core.messages import Messages |
21 | | -from socketsecurity.core.pull_request import resolve_pull_request_context |
| 22 | +from socketsecurity.core.pull_request import ( |
| 23 | + parse_pull_request_number, |
| 24 | + resolve_pull_request_context, |
| 25 | +) |
22 | 26 | from socketsecurity.core.scm_comments import Comments |
23 | 27 | from socketsecurity.core.socket_config import SocketConfig, module_folder_dirs |
24 | 28 | from socketsecurity.core.streaming import StreamingLogs |
@@ -139,8 +143,47 @@ def _select_pull_request_provider(integration_type: str, scm_type: str) -> str: |
139 | 143 | return scm_type if scm_type in ("github", "gitlab") else integration_type |
140 | 144 |
|
141 | 145 |
|
142 | | -def _should_create_scm_diff(event_type: str) -> bool: |
143 | | - return event_type == "diff" |
| 146 | +def create_scm_scan( |
| 147 | + core: Core, |
| 148 | + config: CliConfig, |
| 149 | + scm_event_type: Optional[str], |
| 150 | + *, |
| 151 | + scan_paths: List[str], |
| 152 | + params: FullScanParams, |
| 153 | + no_change: bool, |
| 154 | + base_paths: Optional[List[str]], |
| 155 | + explicit_files: Optional[List[str]], |
| 156 | + external_href: Optional[str], |
| 157 | +) -> Tuple[Diff, bool]: |
| 158 | + """Create the scan for an SCM-integrated run. |
| 159 | +
|
| 160 | + Only a pull request or merge request event has a baseline to compare against, |
| 161 | + so every other pipeline -- default-branch pushes included -- gets a full scan. |
| 162 | + The detected event type is authoritative: API-only diff flags cannot turn an |
| 163 | + ordinary branch pipeline into a comparison. |
| 164 | +
|
| 165 | + Returns the diff and whether it came from a comparison. Callers need the second |
| 166 | + value because a full scan carries no "new alerts" category to comment on or to |
| 167 | + block a build with. |
| 168 | + """ |
| 169 | + scan_kwargs = { |
| 170 | + "no_change": no_change, |
| 171 | + "save_files_list_path": config.save_submitted_files_list, |
| 172 | + "save_manifest_tar_path": config.save_manifest_tar, |
| 173 | + "base_paths": base_paths, |
| 174 | + "explicit_files": explicit_files, |
| 175 | + } |
| 176 | + if scm_event_type == "diff": |
| 177 | + log.info("Starting comment logic for PR/MR event") |
| 178 | + diff = core.create_new_diff( |
| 179 | + scan_paths, params, external_href=external_href, **scan_kwargs |
| 180 | + ) |
| 181 | + return diff, True |
| 182 | + |
| 183 | + log.info("Starting non-PR/MR flow") |
| 184 | + # No before/after pair here, so there is nothing for external_href to hang off. |
| 185 | + diff = core.create_full_scan_with_report_url(scan_paths, params, **scan_kwargs) |
| 186 | + return diff, False |
144 | 187 |
|
145 | 188 |
|
146 | 189 | def build_socket_sdk(config: CliConfig) -> socketdev: |
@@ -507,6 +550,13 @@ def main_code(): |
507 | 550 |
|
508 | 551 | log.info("Continuing with normal scan flow...") |
509 | 552 |
|
| 553 | + # Canonicalize before any adapter reads it. Buildkite always sets |
| 554 | + # BUILDKITE_PULL_REQUEST -- to the string "false" on non-PR builds -- so the |
| 555 | + # documented --pr-number "$BUILDKITE_PULL_REQUEST" form delivers a truthy |
| 556 | + # non-numeric value that GithubConfig would otherwise treat as a real PR, |
| 557 | + # making a branch build look like a pull request event. |
| 558 | + config.pr_number = str(parse_pull_request_number(config.pr_number)) |
| 559 | + |
510 | 560 | scm = None |
511 | 561 | if config.scm == "github": |
512 | 562 | from socketsecurity.core.scm.github import Github, GithubConfig |
@@ -689,6 +739,10 @@ def _is_unprocessed(c): |
689 | 739 | return True |
690 | 740 |
|
691 | 741 | scm_event_type = scm.check_event_type() if scm is not None else None |
| 742 | + # Every branch below except the SCM full-scan one produces a comparison, or |
| 743 | + # is already covered by force_api_mode. See the blocking guard after the |
| 744 | + # scan for why this is tracked. |
| 745 | + comparison_ran = True |
692 | 746 | if scm_event_type == "comment": |
693 | 747 | # FIXME: This entire flow should be a separate command called "filter_ignored_alerts_in_comments" |
694 | 748 | # It's not related to scanning or diff generation - it just: |
@@ -745,18 +799,18 @@ def _is_unprocessed(c): |
745 | 799 |
|
746 | 800 | elif scm is not None and not force_api_mode: |
747 | 801 | log.info("Push initiated flow") |
748 | | - if _should_create_scm_diff(scm_event_type): |
749 | | - log.info("Starting comment logic for PR/MR event") |
750 | | - diff = core.create_new_diff( |
751 | | - scan_paths, |
752 | | - params, |
753 | | - no_change=should_skip_scan, |
754 | | - save_files_list_path=config.save_submitted_files_list, |
755 | | - save_manifest_tar_path=config.save_manifest_tar, |
756 | | - base_paths=base_paths, |
757 | | - explicit_files=scan_explicit_files, |
758 | | - external_href=pr_context.url, |
759 | | - ) |
| 802 | + diff, comparison_ran = create_scm_scan( |
| 803 | + core, |
| 804 | + config, |
| 805 | + scm_event_type, |
| 806 | + scan_paths=scan_paths, |
| 807 | + params=params, |
| 808 | + no_change=should_skip_scan, |
| 809 | + base_paths=base_paths, |
| 810 | + explicit_files=scan_explicit_files, |
| 811 | + external_href=pr_context.url, |
| 812 | + ) |
| 813 | + if comparison_ran: |
760 | 814 | comments = scm.get_comments_for_pr() |
761 | 815 |
|
762 | 816 | # FIXME: this overwrites diff.new_alerts, which was previously populated by Core.create_issue_alerts |
@@ -881,17 +935,6 @@ def _is_unprocessed(c): |
881 | 935 | new_security_comment, |
882 | 936 | new_overview_comment |
883 | 937 | ) |
884 | | - else: |
885 | | - log.info("Starting non-PR/MR flow") |
886 | | - diff = core.create_full_scan_with_report_url( |
887 | | - scan_paths, |
888 | | - params, |
889 | | - no_change=should_skip_scan, |
890 | | - save_files_list_path=config.save_submitted_files_list, |
891 | | - save_manifest_tar_path=config.save_manifest_tar, |
892 | | - base_paths=base_paths, |
893 | | - explicit_files=scan_explicit_files, |
894 | | - ) |
895 | 938 |
|
896 | 939 | output_handler.handle_output(diff) |
897 | 940 |
|
@@ -991,13 +1034,20 @@ def _is_unprocessed(c): |
991 | 1034 | ) |
992 | 1035 | _write_attribution_file(config, all_packages) |
993 | 1036 |
|
994 | | - # If we forced API mode due to no supported files, behave as if --disable-blocking was set |
995 | | - if force_api_mode: |
| 1037 | + # A run that created a full scan instead of a comparison has no baseline, so |
| 1038 | + # diff.new_alerts is not a meaningful thing to block on: with no alert-bearing |
| 1039 | + # output format enabled it is empty, and with --enable-json/--sarif/ |
| 1040 | + # --enable-gitlab-security it holds every alert in the scan rather than the |
| 1041 | + # newly introduced ones. Blocking on it would make the exit code depend on |
| 1042 | + # which output format happened to be requested, so behave as if |
| 1043 | + # --disable-blocking was set. force_api_mode arrives here for the same reason |
| 1044 | + # (no supported manifest files, so nothing to compare). |
| 1045 | + if force_api_mode or not comparison_ran: |
996 | 1046 | if config.strict_blocking: |
997 | 1047 | log.warning("--strict-blocking is only supported in diff mode. " |
998 | | - "API mode (no diff) cannot evaluate existing violations.") |
| 1048 | + "A full scan (no diff) cannot evaluate existing violations.") |
999 | 1049 | if not config.disable_blocking: |
1000 | | - log.debug("Temporarily enabling disable_blocking due to no supported manifest files") |
| 1050 | + log.debug("Temporarily enabling disable_blocking: this run created a full scan, not a comparison") |
1001 | 1051 | config.disable_blocking = True |
1002 | 1052 |
|
1003 | 1053 | # Post commit status to GitLab if enabled |
|
0 commit comments